| File: Debugging\PrintLineDebugger.cs | Web Access |
| Project: Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// 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; #if DEBUG using System.IO; #endif using System.Runtime.CompilerServices; using CommonWriterType = System.Action<string, string, System.Collections.Generic.IEnumerable<string>>; #nullable disable namespace Microsoft.Build.Shared.Debugging; /// <summary> /// A class to help with printline debugging in difficult environments like CI, or when debugging msbuild through other /// tools like VS or CLI. /// See example usages in PrintLineDebugger_Tests /// </summary> internal sealed class PrintLineDebugger : IDisposable { public static Lazy<PrintLineDebugger> Default = new Lazy<PrintLineDebugger>(() => Create(null, null, false)); public static Lazy<PrintLineDebugger> DefaultWithProcessInfo = new Lazy<PrintLineDebugger>(() => Create(null, null, true)); private static CommonWriterType s_writer; #if DEBUG private readonly string _id; #endif private readonly CommonWriterType _writerSetByThisInstance; public PrintLineDebugger(string id, CommonWriterType writer) { #if DEBUG _id = id ?? string.Empty; #endif if (writer != null) { SetWriter(writer); // we wrap the original writer with a locking writer in SetWriter, so get the actual writer that was set _writerSetByThisInstance = GetStaticWriter(); } } public void Dispose() { ReleaseUnmanagedResources(); GC.SuppressFinalize(this); } public static CommonWriterType GetStaticWriter() => s_writer; // this setter is not thread safe because the assumption is that a writer is set once for the duration of the process (or multiple times from different tests which do not run in parallel). public static void SetWriter(CommonWriterType writer) { #if DEBUG var currentWriter = GetStaticWriter(); Assumed.Null(currentWriter, "Cannot set a new writer over an old writer. Remove the old one first"); // wrap with a lock so multi threaded logging does not break messages apart s_writer = LockWrappedWriter; void LockWrappedWriter(string id, string callsite, IEnumerable<string> message) { lock (writer) { writer.Invoke(id, callsite, message); } } #endif } public static void UnsetWriter() { var currentWriter = GetStaticWriter(); Assumed.NotNull(currentWriter, "Cannot unset an already null writer"); s_writer = null; } public static PrintLineDebugger Create( CommonWriterType writer = null, string id = null, bool prependProcessInfo = false) { return new PrintLineDebugger( prependProcessInfo ? $"{DebugUtils.ProcessInfoString}_{id}" : id, writer); } public CommonWriterType GetWriter() { return _writerSetByThisInstance ?? GetStaticWriter(); } public void Log( string message, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { #if DEBUG var writer = GetWriter(); writer?.Invoke(_id, CallsiteString(sourceFilePath, memberName, sourceLineNumber), [message]); #endif } public void Log( IEnumerable<string> args, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0) { #if DEBUG var writer = GetWriter(); writer?.Invoke(_id, CallsiteString(sourceFilePath, memberName, sourceLineNumber), args); #endif } #if DEBUG private static string CallsiteString(string sourceFilePath, string memberName, int sourceLineNumber) { return $"@{Path.GetFileNameWithoutExtension(sourceFilePath)}.{memberName}({sourceLineNumber})"; } #endif private void ReleaseUnmanagedResources() { if (_writerSetByThisInstance != null) { var staticWriter = GetStaticWriter(); Assumed.Equal(staticWriter, _writerSetByThisInstance, $"The writer from this {nameof(PrintLineDebugger)} instance differs from the static writer."); UnsetWriter(); } } ~PrintLineDebugger() { ReleaseUnmanagedResources(); } }