File: BlobLoggerProvider.cs
Web Access
Project: src\src\Logging.AzureAppServices\src\Microsoft.Extensions.Logging.AzureAppServices.csproj (Microsoft.Extensions.Logging.AzureAppServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;
 
namespace Microsoft.Extensions.Logging.AzureAppServices;
 
/// <summary>
/// The <see cref="ILoggerProvider"/> implementation that stores messages by appending them to Azure Blob in batches.
/// </summary>
[ProviderAlias("AzureAppServicesBlob")]
public class BlobLoggerProvider : BatchingLoggerProvider
{
    private readonly IOptionsMonitor<AzureBlobLoggerOptions> _options;
    private readonly Func<string, ICloudAppendBlob> _blobReferenceFactory;
    private readonly HttpClient _httpClient;
 
    /// <summary>
    /// Creates a new instance of <see cref="BlobLoggerProvider"/>
    /// </summary>
    /// <param name="options">The options to use when creating a provider.</param>
    [SuppressMessage("ApiDesign", "RS0022:Constructor make noninheritable base class inheritable", Justification = "Required for backwards compatibility")]
    public BlobLoggerProvider(IOptionsMonitor<AzureBlobLoggerOptions> options)
        : this(options, null)
    {
        _blobReferenceFactory = name => new BlobAppendReferenceWrapper(
            options.CurrentValue.ContainerUrl,
            name,
            _httpClient);
    }
 
    /// <summary>
    /// Creates a new instance of <see cref="BlobLoggerProvider"/>
    /// </summary>
    /// <param name="blobReferenceFactory">The container to store logs to.</param>
    /// <param name="options">Options to be used in creating a logger.</param>
    internal BlobLoggerProvider(
        IOptionsMonitor<AzureBlobLoggerOptions> options,
        Func<string, ICloudAppendBlob> blobReferenceFactory) :
        base(options)
    {
        _options = options;
        _blobReferenceFactory = blobReferenceFactory;
        _httpClient = new HttpClient();
    }
 
    internal override async Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken cancellationToken)
    {
        var eventGroups = messages.GroupBy(GetBlobKey);
        var options = _options.CurrentValue;
        var identifier = options.ApplicationInstanceId + "_" + options.BlobName;
 
        foreach (var eventGroup in eventGroups)
        {
            var key = eventGroup.Key;
            string blobName = options.FileNameFormat(new AzureBlobLoggerContext(
                options.ApplicationName,
                identifier,
                new DateTimeOffset(key.Year, key.Month, key.Day, key.Hour, 0, 0, TimeSpan.Zero)));
 
            var blob = _blobReferenceFactory(blobName);
 
            using (var stream = new MemoryStream())
            using (var writer = new StreamWriter(stream))
            {
                foreach (var logEvent in eventGroup)
                {
                    writer.Write(logEvent.Message);
                }
                writer.Flush();
 
                var tryGetBuffer = stream.TryGetBuffer(out var buffer);
                System.Diagnostics.Debug.Assert(tryGetBuffer);
                await blob.AppendAsync(buffer, cancellationToken).ConfigureAwait(false);
            }
        }
    }
 
    private (int Year, int Month, int Day, int Hour) GetBlobKey(LogMessage e)
    {
        return (e.Timestamp.Year,
            e.Timestamp.Month,
            e.Timestamp.Day,
            e.Timestamp.Hour);
    }
}