|
// 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);
}
}
}
}
|