File: Log\RoslynEventSource.LogBlock.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Diagnostics;
using System.Diagnostics.Tracing;
using System.Threading;
 
namespace Microsoft.CodeAnalysis.Internal.Log;
 
internal sealed partial class RoslynEventSource
{
    /// <summary>
    /// Logs an informational block with given <paramref name="entity"/>'s <see cref="object.ToString"/> representation as the message
    /// and specified <paramref name="functionId"/>.
    /// On dispose of the returned disposable object, it logs the 'tick' count between the start and end of the block.
    /// Unlike other logging methods on <see cref="RoslynEventSource"/>, this method does not check
    /// if the specified <paramref name="functionId"/> was explicitly enabled.
    /// Instead it checks if the <see cref="RoslynEventSource"/> was enabled at <see cref="EventLevel.Informational"/> level.
    /// </summary>
    public static LogBlock LogInformationalBlock(FunctionId functionId, object entity, CancellationToken cancellationToken)
        => LogBlock.Create(functionId, entity, EventLevel.Informational, cancellationToken);
 
    /// <summary>
    /// Logs an informational message block with the given <paramref name="message"/>> and specified <paramref name="functionId"/>.
    /// On dispose of the returned disposable object, it logs the 'tick' count between the start and end of the block.
    /// Unlike other logging methods on <see cref="RoslynEventSource"/>, this method does not check
    /// if the specified <paramref name="functionId"/> was explicitly enabled.
    /// Instead it checks if the <see cref="RoslynEventSource"/> was enabled at <see cref="EventLevel.Informational"/> level.
    /// </summary>
    public static LogBlock LogInformationalBlock(FunctionId functionId, string message, CancellationToken cancellationToken)
        => LogBlock.Create(functionId, message, EventLevel.Informational, cancellationToken);
 
    /// <summary>
    /// This tracks the logged message. On instantiation, it logs 'Started block' with other event data.
    /// On dispose, it logs 'Ended block' with the same event data so we can track which block started and ended when looking at logs.
    /// </summary>
    internal struct LogBlock : IDisposable
    {
        private readonly FunctionId _functionId;
        private readonly object? _entityForMessage;
        private readonly EventLevel _eventLevel;
        private readonly int _blockId;
        private readonly CancellationToken _cancellationToken;
 
        private readonly int _tick;
        private bool _startLogged;
        private string? _message;
 
        /// <summary>
        /// next unique block id that will be given to each LogBlock
        /// </summary>
        private static int s_lastUniqueBlockId;
 
        private LogBlock(
            FunctionId functionId,
            string? message,
            object? entityForMessage,
            EventLevel eventLevel,
            int blockId,
            CancellationToken cancellationToken)
        {
            Debug.Assert(message != null || entityForMessage != null);
 
            _functionId = functionId;
            _message = message;
            _entityForMessage = entityForMessage;
            _eventLevel = eventLevel;
            _blockId = blockId;
            _cancellationToken = cancellationToken;
            _tick = Environment.TickCount;
            _startLogged = false;
        }
 
        public static LogBlock Create(
            FunctionId functionId,
            object entityForMessage,
            EventLevel eventLevel,
            CancellationToken cancellationToken)
        {
            var blockId = GetNextUniqueBlockId();
            var logBlock = new LogBlock(functionId, message: null, entityForMessage, eventLevel, blockId, cancellationToken);
            logBlock.OnStart();
            return logBlock;
        }
 
        public static LogBlock Create(
            FunctionId functionId,
            string message,
            EventLevel eventLevel,
            CancellationToken cancellationToken)
        {
            var blockId = GetNextUniqueBlockId();
            var logBlock = new LogBlock(functionId, message, entityForMessage: null, eventLevel, blockId, cancellationToken);
            logBlock.OnStart();
            return logBlock;
        }
 
        /// <summary>
        /// return next unique pair id
        /// </summary>
        private static int GetNextUniqueBlockId()
            => Interlocked.Increment(ref s_lastUniqueBlockId);
 
        private void OnStart()
        {
            if (EnsureMessageIfLoggingEnabled())
            {
                Debug.Assert(_message != null);
                Debug.Assert(!_startLogged);
 
                Instance.BlockStart(_message, _functionId, _blockId);
                _startLogged = true;
            }
        }
 
        private bool EnsureMessageIfLoggingEnabled()
        {
            if (Instance.IsEnabled(_eventLevel, EventKeywords.None))
            {
                _message ??= (_entityForMessage?.ToString() ?? string.Empty);
                return true;
            }
 
            return false;
        }
 
        public void Dispose()
        {
            if (!EnsureMessageIfLoggingEnabled())
            {
                return;
            }
 
            Debug.Assert(_message != null);
 
            if (!_startLogged)
            {
                // User enabled logging after the block start.
                // We log a block start to log the message along with the block ID.
                Instance.BlockStart(_message, _functionId, _blockId);
                _startLogged = true;
            }
 
            // This delta is valid for durations of < 25 days
            var delta = Environment.TickCount - _tick;
 
            if (_cancellationToken.IsCancellationRequested)
            {
                Instance.BlockCanceled(_functionId, delta, _blockId);
            }
            else
            {
                Instance.BlockStop(_functionId, delta, _blockId);
            }
        }
    }
}