Skip to content

[Bug] Exception stack trace by Spectre console wraps horribly in MSBuild #10945

@KalleOlaviNiemitalo

Description

@KalleOlaviNiemitalo

Describe the bug

When I run docfx build from within MSBuild, and DocFX terminates because of an exception, the stack trace is difficult to read because of numerous line breaks at unexpected positions.

To Reproduce

Steps to reproduce the behavior:

  1. Use Git Bash on Windows.
  2. mkdir -p /c/Projects/docfx-demo
  3. cd /c/Projects/docfx-demo
  4. dotnet new global.json --sdk-version=8.0.100 --roll-forward=feature
  5. dotnet new tool-manifest
  6. dotnet tool install --local --version=2.78.4 docfx
  7. dotnet docfx init. Accept defaults, except answer n to Generate .NET API documentation? [y/n] (y) and Enable PDF? [y/n] (y).
  8. Create docs.msbuildproj with this content:
    <Project Sdk="Microsoft.Build.NoTargets/3.7.56">
      <PropertyGroup>
        <TargetFramework>netstandard2.0</TargetFramework>
      </PropertyGroup>
    
      <Target Name="DocFx" AfterTargets="Build">
        <Delete Files="obj/docfx.log.json" />
        <Exec UseCommandProcessor="false" Command='dotnet docfx build --log "obj/docfx.log.json"' />
      </Target>
    </Project>
  9. dotnet build docs.msbuildproj # Succeeds.
  10. exec 9< _site/index.html # Open the file to provoke IOException.
  11. dotnet build docs.msbuildproj # Fails, and shows a stack trace.

Current behavior

The stack trace of the exception is difficult to read, because of excessive line breaks both between and within words:

$ dotnet build docs.msbuildproj
  Determining projects to restore...
  All projects are up-to-date for restore.
  Searching built-in plugins in directory C:\Users\Kalle\.nuget\packages\docfx\2.78.4\tools\
  net8.0\any\...
  Post processor ExtractSearchIndex loaded.
  No files are found with glob pattern images/**, excluding <none>, under directory "C:\Proj
  ects\docfx-demo"
  7 plug-in(s) loaded.
  ExtractSearchIndex: UseMetadata = False, UseMetadataTitle = True
  Building 2 file(s) in TocDocumentProcessor(BuildTocDocument)...
  Building 3 file(s) in ConceptualDocumentProcessor(BuildConceptualDocument=>ValidateConcept
  ualDocumentMetadata)...
  Applying templates to 5 model(s)...
  AggregateException: One or more errors occurred. (The process cannot access
   the
  file 'C:\Projects\docfx-demo\_site\index.html' because it is being used by
  another process.)
       IOException: The process cannot access the file

       'C:\Projects\docfx-demo\_site\index.html' because it is being used by
       another process.
         at SafeFileHandle CreateFile(
string fullPath, FileMode mode, FileAccess

            access, FileShare share, FileOptions
 options)
         at SafeFileHandle Open(stri
  ng fullPath, FileMode mode, FileAccess
38;5;8maccess,
            FileShare share, FileOptions options
, long preallocationSize,
            UnixFileMode? unixCreateMode)

         at ctor(string path,
FileMode mode, FileAccess access, Fil
  eShare share,
            FileOptions options, long preallocatio
  nSize, UnixFileMode?
            unixCreateMode)

         at FileStreamStrategy ChooseStrategyCore(
0mstring path, FileMode mode,
            FileAccess access, FileShare share
  , FileOptions options, long
            preallocationSize, UnixFileMode? unixCreateMode
)
         at FileStream Create(string
   path)
         at Stream Create(RelativePa
  th file) in RealFileWriter.cs:
27
         at Stream Create(RelativePa
  th file) in FileAbstractLayer.cs
  :46
         at Stream Create(string
   file) in FileAbstractLayer.cs:
38;5;251m82
         at Stream Create(string
   file) in RootedFileAbstractLayer.cs
  :34
         at void TransformDocument(s
  tring result, string extension,
            IDocumentBuildContext context, string
  destFilePath, ManifestItem
            manifestItem, out List<XRefDetails> unresolvedXRefs
) in
            TemplateModelTransformer.cs:268

         at ManifestItem Transform(I
  nternalManifestItem item) in
            TemplateModelTransformer.cs:151

         at void <ProcessCore>b__0(I
  nternalManifestItem item) in
            TemplateProcessor.cs:190

         at void <RunAll>b__0(TEleme
  nt s) in DocumentExceptionExtensions.cs
:91
         at void <ForWorker>b__1(ref
   RangeWorker currentWorker, long timeout, ou
  t
            bool replicationDelegateYieldedBeforeCompletion)

         at void <ForWorker>b__1(ref
   RangeWorker currentWorker, long timeout, ou
  t
            bool replicationDelegateYieldedBeforeCompletion)

         at void Execute()

    at void Run<TState>(Replicatable
  UserAction<TState> action, ParallelOptions
       options, bool stopOnFirstFailure)

    at ParallelLoopResult ForWorker<TLocal,TInt>(
TInt fromInclusive, TInt
       toExclusive, ParallelOptions parallelOptions,
Action<TInt> body,
       Action<TInt, ParallelLoopState> bodyWithState, Func<TInt
  ,
       ParallelLoopState, TLocal, TLocal> bodyWithLocal, Func<T
  Local> localInit,
       Action<TLocal> localFinally)

    at ParallelLoopResult ForWorker<TLocal,TInt>(
TInt fromInclusive, TInt
       toExclusive, ParallelOptions parallelOptions,
Action<TInt> body,
       Action<TInt, ParallelLoopState> bodyWithState, Func<TInt
  ,
       ParallelLoopState, TLocal, TLocal> bodyWithLocal, Func<T
  Local> localInit,
       Action<TLocal> localFinally)

    at ParallelLoopResult ForEachWorker<TSource,TLocal>
(IEnumerable<TSource>
       source, ParallelOptions parallelOptions, Ac
  tion<TSource> body,
       Action<TSource, ParallelLoopState> bodyWithState, Action
  <TSource,
       ParallelLoopState, long> bodyWithStateAndIndex, Func<TSo
  urce,
       ParallelLoopState, TLocal, TLocal> bodyWithStateAndLocal,
Func<TSource,
       ParallelLoopState, long, TLocal, TLocal> bodyWithEverything,
Func<TLocal>
       localInit, Action<TLocal> localFinally)

    at ParallelLoopResult ForEach<TSource>(
IEnumerable<TSource> source,
       ParallelOptions parallelOptions, Action<TSource>
body)
    at void RunAll<TElement>(IEnumer
  able<TElement> elements, Action<TElement>
       action, int parallelism, CancellationToken
0m cancellationToken) in
       DocumentExceptionExtensions.cs:80

    at void RunAll<TElement>(IReadOn
  lyList<TElement> elements, Action<TElement>
       action, int parallelism, CancellationToken
0m cancellationToken) in
       DocumentExceptionExtensions.cs:68

    at List<ManifestItem> ProcessCore(
List<InternalManifestItem> items,
       ApplyTemplateSettings settings, IDictionary<string, obje
  ct> globals) in
       TemplateProcessor.cs:185

    at List<ManifestItem> Process(Li
  st<InternalManifestItem> manifest,
       ApplyTemplateSettings settings, IDictionary<string, obje
  ct> globals) in
       TemplateProcessor.cs:82

    at List<ManifestItem> ProcessTemplate()
in ManifestProcessor.cs:186
    at void Process() in
ManifestProcessor.cs:53
    at void Handle(List<HostService>
   hostServices, int maxParallelism)
in
       LinkPhaseHandler.cs:32

    at Manifest Build(DocumentBuildP
  arameters parameters, IMarkdownService
       markdownService, CancellationToken cancellationToken
) in
       SingleDocumentBuilder.cs:70

    at void Build(IList<DocumentBuil
  dParameters> parameters, string
       outputDirectory, CancellationToken cancellationToken
) in DocumentBuilder.cs
       :123

    at void BuildDocument(BuildJsonC
  onfig config, BuildOptions options,
       TemplateManager templateManager, string bas
  eDirectory, string
       outputDirectory, string templateDirectory,
  CancellationToken
       cancellationToken) in DocumentBuilderWrapper
  .cs:42
    at string Exec(BuildJsonConfig
 config, BuildOptions options, string

       configDirectory, string outputDirectory, Ca
  ncellationToken
       cancellationToken) in RunBuild.cs
:39
    at void <Execute>b__0() in
BuildCommand.cs:25
    at int Run(LogOptions
options, Action run) in Com
  mandHelper.cs:48
    at int Execute(CommandContext
 context, BuildCommandOptions settings)
in
       BuildCommand.cs:15

    at Task<int> Execute(CommandCont
  ext context, CommandSettings settings)
in
       CommandOfT.cs:40

    at async Task<int> Execute(Comma
  ndTree leaf, CommandTree tree, CommandContex
  t
       context, ITypeResolver resolver, IConfigura
  tion configuration) in
       CommandExecutor.cs:259

C:\Projects\docfx-demo\docs.msbuildproj(8,5): error MSB3073: The command "dotnet docfx build --log "ob
j/docfx.log.json"" exited with code -1.

Build FAILED.

C:\Projects\docfx-demo\docs.msbuildproj(8,5): error MSB3073: The command "dotnet docfx build --log "ob
j/docfx.log.json"" exited with code -1.
    0 Warning(s)
    1 Error(s)

Time Elapsed 00:00:03.10

There is some highlighting of method names and parameter names; perhaps that causes MSBuild to overestimate the lengths of the lines:

Image

Expected behavior

Display a readable stack trace.

A documented command-line option that disables Spectre.Console output formatting altogether and just writes the plain Exception.ToString() to Console.Error would suffice. It would be easy to add that to MSBuild Exec task invocations.

Context (please complete the following information):

  • OS: Windows 11 version 25H2
  • Docfx version: [e.g. 2.59.0] 2.78.4
  • MSBuild version: 17.11.48+02bf66295
  • .NET SDK version: 8.0.416 (roll-forward from 8.0.100)

Additional context

Related to #9457, but that issue was for warnings, while this one is for exception stack traces.

Adding EnvironmentVariables="NO_COLOR=1" or EnvironmentVariables="TERM=dumb" to the Exec task invocation makes the output a lot nicer. (NO_COLOR=1 still uses a bold font for exception messages and file names, whle TERM=dumb doesn't.) However, DocFX does not document these, and the stack trace still has extra line breaks if the console is fewer than 83 columns wide.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions