File: Implementation\FileSystemTelemetryBlobStorage.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 OpenTelemetry.PersistentStorage.Abstractions;
using OpenTelemetry.PersistentStorage.FileSystem;

namespace Microsoft.DotNet.Cli.Telemetry.Implementation;

/// <summary>
/// <see cref="ITelemetryBlobStorage"/> backed by the
/// <c>OpenTelemetry.PersistentStorage.FileSystem</c> <see cref="FileBlobProvider"/>. Blobs
/// are stored as files under a per-user telemetry storage directory. This is a public copy
/// of the same library the Azure Monitor exporter vendors internally, so it has its own
/// storage directory and does not conflict with the exporter's.
/// </summary>
internal sealed class FileSystemTelemetryBlobStorage : ITelemetryBlobStorage
{
    // FileBlobProvider creates the storage directory in its constructor, so defer building it
    // until telemetry is actually persisted or drained. Registering the exporter happens even
    // when telemetry is opted out (the registration runs in a static constructor, before the
    // per-invocation opt-out check), and merely registering it must not create the ~/.dotnet
    // telemetry folder. Lazy<T> defaults to thread-safe (ExecutionAndPublication) initialization,
    // which matters because the synchronous export path and the background drain can first touch
    // the provider concurrently.
    private readonly Lazy<PersistentBlobProvider> _provider;

    public FileSystemTelemetryBlobStorage(string storageDirectory)
    {
        _provider = new Lazy<PersistentBlobProvider>(() => new FileBlobProvider(storageDirectory));
    }

    public bool TryPersist(byte[] data)
        => _provider.Value.TryCreateBlob(data.AsSpan(), out _);

    public IEnumerable<ITelemetryBlob> GetBlobs()
    {
        foreach (var blob in _provider.Value.GetBlobs())
        {
            yield return new FileSystemTelemetryBlob(blob);
        }
    }

    private sealed class FileSystemTelemetryBlob(PersistentBlob blob) : ITelemetryBlob
    {
        public bool TryLease(int leasePeriodMilliseconds) => blob.TryLease(leasePeriodMilliseconds);

        public bool TryRead(out byte[]? data) => blob.TryRead(out data);

        public bool TryRelease()
        {
            if (blob is not FileBlob { FullPath: var lockedPath }
                || !lockedPath.EndsWith(".lock", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }

            var atSignIndex = lockedPath.LastIndexOf('@');
            if (atSignIndex < 0)
            {
                return false;
            }

            try
            {
                File.Move(lockedPath, lockedPath[..atSignIndex]);
                return true;
            }
            catch
            {
                return false;
            }
        }

        public bool TryDelete() => blob.TryDelete();
    }
}