Skip to content

Conversation

@filzrev
Copy link
Contributor

@filzrev filzrev commented Jan 11, 2026

Currently ArtifactsDirectory and generated log file path is not shown on console.
And it need to find log files from bin\Debug\net8.0\BenchmarkDotNet.Artifacts.

This PR add feature to show following paths as clickable link when benchmark finished.

  • ArtifactsDirectory
  • LogFilePath

This behavior is skipped when --disableLogFile is passed.

Sample Image
image

What's Changed in this PR

  1. Add Helpers/ConsoleHelper.cs to output clickable link by using ANSI Escape Sequence OSC8.
    When stdout is redirected or terminal don't support OSC8. It write URL as plain text.
  2. Add Helpers/PathHelper.cs to gets relative path. (It's required because netstandard2.0 don't support Path.GetRelativePath)
  3. Modify CompositeLogger.cs and add code to get ConsoleLogger-like logger.
  4. Modify BenchmarkRunnerClean.cs and add code to output clickable links.
  5. Add Helpers/PathHelperTests.cs unit tests for PathHelper.GetRelativePath.

Other Changes

  • Modify ConfigParser.cs to suppress exception thrown when running on VS TestExplorer.
  • Modify BenchmarkRunnerClean.cs and insert empty line after Global total time.

@filzrev filzrev force-pushed the feat-show-logfilepath-when-benchmark-finished branch 2 times, most recently from 3c13bae to 0f8e811 Compare January 11, 2026 09:45
@timcassell
Copy link
Collaborator

timcassell commented Jan 12, 2026

There appears to be a bit of overlap with TaskbarProgress. Can you move common functionality to a separate helper (or to ConsoleHelper)?

@timcassell
Copy link
Collaborator

I wonder if we should add a method to ILogger interface so custom loggers can also participate in links (it's a shame netstandard2.0 doesn't support DIMs, so it would be a breaking change).

@timcassell
Copy link
Collaborator

It would also be good to use this for IDiagnoser.DisplayResults implementations (e.g. DisassemblyDiagnoser, etc.) (more reason to add it to ILogger interface I guess).

@filzrev
Copy link
Contributor Author

filzrev commented Jan 12, 2026

I wonder if we should add a method to ILogger interface so custom loggers can also participate in links (it's a shame netstandard2.0 doesn't support DIMs, so it would be a breaking change).

ANSI escape sequence is not correctly shown when written to non-console logger.
And clickable link is useful for TerminalLogger (FileLogger).
So I've output clickable links for ConsoleLogger-like logger only.

It would also be good to use this for IDiagnoser.DisplayResults implementations (e.g. DisassemblyDiagnoser, etc.) (more reason to add it to ILogger interface I guess).

As described above. I want to use this feature with custom TeraminalLogger/QuietLogger.
So it is desirable that this be executed at the end of processing.

There appears to be a bit of overlap with TaskbarProgress. Can you move common functionality to a separate helper (or to ConsoleHelper)?
I'll check TaskbarProgress implementation later.

@timcassell
Copy link
Collaborator

ANSI escape sequence is not correctly shown when written to non-console logger.

Non-console loggers would implement the link as they see fit. You'd just pass the raw link, not the escape sequence.

@filzrev
Copy link
Contributor Author

filzrev commented Jan 12, 2026

I've tried to extend ILogger interface.
But without Default Interface Methods feature.
it require a lot of additional code to support WriteLink API. and it affects existing thirdparty custom loggers.

So I wish to keep existing implementation direction.

There appears to be a bit of overlap with TaskbarProgress.

I've confirmed TaskbarProgress API.
But it seems there aren't many duplicate parts. (Only GetConsoleMode API and related constants. and )
It might be better to merge ANSI Escape Sequence handling code and Console related APIs in future.
But for now, I'd like to leave it as is.

Additionally, I've removed code that output link with markdown link syntax(82e72e0).
Because it's hard to read when caption and link path is long text.

@timcassell
Copy link
Collaborator

timcassell commented Jan 12, 2026

What about add a new interface ILinkLogger : ILogger that we can type-check for? Then we can add an ILogger extension that handles it.

@filzrev
Copy link
Contributor Author

filzrev commented Jan 15, 2026

What about add a new interface ILinkLogger : ILogger that we can type-check for? Then we can add an ILogger extension that handles it.

I've modified following codes at 956d44c

  • Add IConsoleLogger: ILogger interface and define related extension methods.
  • Separate logics that relating to ANSI Escape Sequences to ConsoleHelper.
  • Use StringBuilder to output clickable link line. Because it may be called from multiple thread in future.
  • Change code to resolve IConsoleLogger from FirstOrDefault to SingleOrDefault.
    Because logger that write to stdout should not be multiple instance.

@timcassell
Copy link
Collaborator

I was thinking more like:

public interface ILinkLogger : ILogger
{
    void WriteLink(string link, string? linkCaption = null, LogKind logKind = LogKind.Info);
}

public static class ILoggerExtensions
{
    public void WriteLink(this ILogger logger, string link, string? linkCaption = null, LogKind logKind = LogKind.Info)
        => logger is ILinkLogger linkLogger
            ? linkLogger.WriteLink(link, linkCaption, logKind)
            : logger.Write(logKind, link);
}

And ConsoleLogger implements it with the escape sequences. Because the console is just a UI, a different UI may display links differently.

We don't need prefix and suffix because you can just use logger.Write(prefix); logger.WriteLink(...); logger.Write(suffix);.

CompositeLogger can implement ILinkLogger and just call the extension on each logger.

@filzrev
Copy link
Contributor Author

filzrev commented Jan 20, 2026

I've updated code based on review comment.

We don't need prefix and suffix because you can just use logger.Write(prefix); logger.WriteLink(...); logger.Write(suffix);

It's required for temporary workaround for Windows Terminal inside helper method.
And when using multiple Write to console. Ot might be mixed when another thread call directly Console.Write/WriteLine.


ILoggerExtensions is exposed with public visibility.
This is because it can be used for generic purposes.

@timcassell
Copy link
Collaborator

I'm not concerned about race conditions. Loggers are already assumed to not be thread-safe. In the one place we do parallel processing (builds), we use NullLogger to avoid the issue.

I don't like the new interface just being a marker. I feel like you ignored my comment

Because the console is just a UI, a different UI may display links differently.

@filzrev filzrev force-pushed the feat-show-logfilepath-when-benchmark-finished branch from 9891df6 to 19c8f03 Compare January 22, 2026 10:59
@filzrev
Copy link
Contributor Author

filzrev commented Jan 22, 2026

I don't like the new interface just being a marker. I feel like you ignored my comment

I've removed related doc comment on latest commit.

Because the console is just a UI, a different UI may display links differently.

WriteLink method should be implemented as ILogger interface method.
But currently it's implemented as ILogger's extension method to support netstandard 2.0 .
(It require a lot of tasks without default interface method method)

And currently ILinkLogger is just be used as marker interface to distinguish logger that support ANSI escape sequence and OSC8.
To support other type of UI. It need to add another interface. (e.g. To support clickable link for VSTestLogger. It need to add file:/// prefix)

@timcassell
Copy link
Collaborator

That doesn't make sense, why would you use separate marker interfaces for every implementation? If you put the method on the interface only 1 interface is needed; the console logger can implement it with escape sequence, and VS test logger can implement it with the file prefix. The extension exists simply to simulate DIM for old runtimes.

@filzrev
Copy link
Contributor Author

filzrev commented Jan 24, 2026

What doesn't make sense, why would you use separate marker interfaces for every implementation?

Ignore above my comment.
I re-read my own comments, and there is no need to implement a different marker interface.

My original intent was that if new type of ILinkLogger type is added.
It need to conditionaly switch by type inside extension method.
So ILoggerExtension need to know all ILinkLogger` derived classes.

It can be avoided when using Default Interface Implementation or changing logger base class something like LoggerBase class.
But it's over engineering because additional ILinkLogger is not expected to be implemented.

@timcassell
Copy link
Collaborator

It can be avoided when using Default Interface Implementation or changing logger base class something like LoggerBase class.

That's what the extension is supposed to do, simulate DIM (if the logger doesn't implement ILinkLogger, it uses a default implementation, otherwise it calls the interface method).

But it's over engineering because additional ILinkLogger is not expected to be implemented.

It is expected to be implemented, though? You just gave an example of console logger and VS test logger needing different implementations. Custom user loggers could also have different implementations that we don't know about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants