Skip to content

Use MSBuild Logging APIs for Task output instead of StdOut #933

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
297885d
Add MSBuild logger
sfoslund Sep 26, 2024
54fc2d2
Sidestep a timing issue with the TaskLoggingHelper in the GenerateSbo…
baronfel Oct 1, 2024
d4c1f08
Ensure that for all TFMs we can infer a verbosity and apply it to the…
baronfel Feb 16, 2025
e11db02
remove unused code
baronfel Feb 16, 2025
4f0ff8d
reduce verbosity of pre-existing folder check to prevent errant MSBui…
baronfel Feb 23, 2025
437863a
respond to review feedback
baronfel Feb 23, 2025
39a580e
respond to some logging feedback
baronfel Feb 26, 2025
f7732c2
disable component detection summary
baronfel Feb 27, 2025
2f9d59e
transform component detection warnings into information because they …
baronfel Feb 27, 2025
065fb37
Fix infinite loop
baronfel Feb 28, 2025
0874fd8
fix rewriting detection
baronfel Feb 28, 2025
29ba98c
Keep some of the previous API surface area to minimize breaks. Add do…
baronfel Mar 1, 2025
d4442d4
fix potential nonexistent dictionary access
baronfel Mar 1, 2025
05b7d47
Elevate Microsoft.Extensions.Options to a direct dependency to preven…
baronfel Mar 2, 2025
745fe5b
make build
Forgind Mar 31, 2025
1290105
Merge branch 'main' into msbuild-based-logging
Forgind Apr 5, 2025
a70ff15
Update build-test-tool-template.yaml
Forgind Apr 7, 2025
0f400f5
Add dependencies
Forgind Apr 8, 2025
9644f28
Add version for M.E.Primitives
Forgind Apr 8, 2025
356fef7
Revert version change
Forgind Apr 9, 2025
b2bd375
Little tweaks
Forgind Apr 11, 2025
3a788ad
Add a couple package refs
Forgind Apr 11, 2025
ce545c8
Extend reach of PackageRef
Forgind Apr 11, 2025
ba73bea
Remove duplicates
Forgind Apr 11, 2025
68f73bb
One more
Forgind Apr 11, 2025
558380d
Push for up-to-date .NET?
Forgind Apr 11, 2025
c1332fd
Take 2
Forgind Apr 11, 2025
72feabd
And 3
Forgind Apr 11, 2025
17fb04e
No -build switch
Forgind Apr 11, 2025
9b87d43
NoPack
Forgind Apr 11, 2025
8763126
Use pwsh for the PR build.
MiYanni Apr 12, 2025
755e91d
Merge pull request #2 from MiYanni/msbuild-based-logging
Forgind Apr 14, 2025
7034219
net8.0-->net472
Forgind Apr 14, 2025
6ade344
Merge branch 'msbuild-based-logging' of https://github.com/baronfel/s…
Forgind Apr 14, 2025
5eed0aa
Revert "net8.0-->net472"
Forgind Apr 15, 2025
848b13c
Disable tests
Forgind Apr 15, 2025
4b90c4a
Delete fully
Forgind Apr 15, 2025
264b289
Sigh
Forgind Apr 15, 2025
9ef7330
reduce changes in yaml
Forgind Apr 17, 2025
7ba11f2
More reduce
Forgind Apr 17, 2025
cb77851
Merge branch 'main' into msbuild-based-logging
Forgind Jun 27, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
</PropertyGroup>

<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" PrivateAssets="all"/>
<PackageReference Include="MinVer" PrivateAssets="all"/>
<PackageReference Include="StyleCop.Analyzers" PrivateAssets="all"/>
Expand Down
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.Primitives" Version="8.0.0" />
<PackageVersion Include="Microsoft.IO.Redist" Version="6.1.0" />
<PackageVersion Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.12.19" />
<PackageVersion Include="MinVer" Version="6.0.0" />
Expand Down
25 changes: 4 additions & 21 deletions pipelines/build-test-tool-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,19 @@ steps:
inputs:
useGlobalJson: true

- task: DotNetCoreCLI@2
- pwsh: dotnet restore --configfile nuget.config
displayName: 'Restore solution'
inputs:
command: restore
feedsToUse: config
nugetConfigPath: nuget.config
verbosityRestore: Normal

- task: DotNetCoreCLI@2
- pwsh: dotnet build --configuration $(BuildConfiguration) --no-restore
displayName: Build
inputs:
arguments: '-c $(BuildConfiguration)'

- task: DotNetCoreCLI@2
- pwsh: dotnet test --no-build --configuration $(BuildConfiguration) -- --report-trx --results-directory $(Agent.TempDirectory) --coverage --coverage-output $(Agent.TempDirectory)/coverage.cobertura.xml --coverage-output-format cobertura
displayName: Run unit tests (with coverage on Windows)
condition: eq(variables['Agent.OS'], 'Windows_NT')
inputs:
command: 'test'
nobuild: true
configuration: '$(BuildConfiguration)'
arguments: '-- --report-trx --results-directory $(Agent.TempDirectory) --coverage --coverage-output $(Agent.TempDirectory)/coverage.cobertura.xml --coverage-output-format cobertura'

- task: DotNetCoreCLI@2
- pwsh: dotnet test --no-build --configuration $(BuildConfiguration) -- --report-trx --results-directory $(Agent.TempDirectory)
displayName: Run unit tests (without coverage on non-Windows)
condition: ne(variables['Agent.OS'], 'Windows_NT')
inputs:
command: 'test'
nobuild: true
configuration: '$(BuildConfiguration)'
arguments: '-- --report-trx --results-directory $(Agent.TempDirectory)'

# While DotNetCoreCLI docs say that it publishes both TRX and coverage, it doesn't actually publish coverage.
# https://github.com/microsoft/azure-pipelines-tasks/issues/18254
Expand Down
2 changes: 2 additions & 0 deletions src/Microsoft.Sbom.Adapters/Microsoft.Sbom.Adapters.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<PackageReference Include="Microsoft.ComponentDetection.Contracts" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="packageurl-dotnet" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Primitives" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Microsoft.Sbom.Api.Utils;
using Microsoft.Sbom.Common;
using Microsoft.Sbom.Common.Config;
using Microsoft.Sbom.Contracts;
using Microsoft.Sbom.Extensions;
using Constants = Microsoft.Sbom.Api.Utils.Constants;
using ILogger = Serilog.ILogger;
Expand All @@ -34,6 +35,7 @@ public abstract class ComponentDetectionBaseWalker
private readonly ISbomConfigProvider sbomConfigs;
private readonly IFileSystemUtils fileSystemUtils;
private readonly ILicenseInformationFetcher licenseInformationFetcher;
private readonly RuntimeConfiguration? runtimeConfiguration;
private readonly IPackageDetailsFactory packageDetailsFactory;

public ConcurrentDictionary<string, string> LicenseDictionary = new ConcurrentDictionary<string, string>();
Expand All @@ -48,7 +50,8 @@ public ComponentDetectionBaseWalker(
ISbomConfigProvider sbomConfigs,
IFileSystemUtils fileSystemUtils,
IPackageDetailsFactory packageDetailsFactory,
ILicenseInformationFetcher licenseInformationFetcher)
ILicenseInformationFetcher licenseInformationFetcher,
RuntimeConfiguration? runtimeConfiguration)
{
this.log = log ?? throw new ArgumentNullException(nameof(log));
this.componentDetector = componentDetector ?? throw new ArgumentNullException(nameof(componentDetector));
Expand All @@ -57,6 +60,7 @@ public ComponentDetectionBaseWalker(
this.fileSystemUtils = fileSystemUtils ?? throw new ArgumentNullException(nameof(fileSystemUtils));
this.packageDetailsFactory = packageDetailsFactory ?? throw new ArgumentNullException(nameof(packageDetailsFactory));
this.licenseInformationFetcher = licenseInformationFetcher ?? throw new ArgumentNullException(nameof(licenseInformationFetcher));
this.runtimeConfiguration = runtimeConfiguration;
}

public (ChannelReader<ScannedComponent> output, ChannelReader<ComponentDetectorException> error) GetComponents(string buildComponentDirPath)
Expand Down Expand Up @@ -112,6 +116,10 @@ async Task Scan(string path)
var cmdLineParams = configuration.ToComponentDetectorCommandLineParams(cliArgumentBuilder);

var scanSettings = cliArgumentBuilder.BuildScanSettingsFromParsedArgs(cmdLineParams);
if (runtimeConfiguration != null)
{
scanSettings.NoSummary = runtimeConfiguration.NoComponentGovernanceSummary;
}

var scanResult = await componentDetector.ScanAsync(scanSettings);

Expand Down
5 changes: 3 additions & 2 deletions src/Microsoft.Sbom.Api/Executors/PackagesWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Sbom.Api.Utils;
using Microsoft.Sbom.Common;
using Microsoft.Sbom.Common.Config;
using Microsoft.Sbom.Contracts;
using Microsoft.Sbom.Extensions;
using ILogger = Serilog.ILogger;

Expand All @@ -19,8 +20,8 @@ namespace Microsoft.Sbom.Api.Executors;
/// </summary>
public class PackagesWalker : ComponentDetectionBaseWalker
{
public PackagesWalker(ILogger log, ComponentDetectorCachedExecutor componentDetector, IConfiguration configuration, ISbomConfigProvider sbomConfigs, IFileSystemUtils fileSystemUtils, IPackageDetailsFactory packageDetailsFactory, ILicenseInformationFetcher licenseInformationFetcher)
: base(log, componentDetector, configuration, sbomConfigs, fileSystemUtils, packageDetailsFactory, licenseInformationFetcher)
public PackagesWalker(ILogger log, ComponentDetectorCachedExecutor componentDetector, IConfiguration configuration, ISbomConfigProvider sbomConfigs, IFileSystemUtils fileSystemUtils, IPackageDetailsFactory packageDetailsFactory, ILicenseInformationFetcher licenseInformationFetcher, RuntimeConfiguration runtimeConfiguration = null)
: base(log, componentDetector, configuration, sbomConfigs, fileSystemUtils, packageDetailsFactory, licenseInformationFetcher, runtimeConfiguration)
{
}

Expand Down
5 changes: 3 additions & 2 deletions src/Microsoft.Sbom.Api/Executors/SbomComponentsWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Microsoft.Sbom.Api.Utils;
using Microsoft.Sbom.Common;
using Microsoft.Sbom.Common.Config;
using Microsoft.Sbom.Contracts;
using Microsoft.Sbom.Extensions;
using ILogger = Serilog.ILogger;

Expand All @@ -19,8 +20,8 @@ namespace Microsoft.Sbom.Api.Executors;
/// </summary>
public class SbomComponentsWalker : ComponentDetectionBaseWalker
{
public SbomComponentsWalker(ILogger log, ComponentDetectorCachedExecutor componentDetector, IConfiguration configuration, ISbomConfigProvider sbomConfigs, IFileSystemUtils fileSystemUtils, IPackageDetailsFactory packageDetailsFactory, ILicenseInformationFetcher licenseInformationFetcher)
: base(log, componentDetector, configuration, sbomConfigs, fileSystemUtils, packageDetailsFactory, licenseInformationFetcher)
public SbomComponentsWalker(ILogger log, ComponentDetectorCachedExecutor componentDetector, IConfiguration configuration, ISbomConfigProvider sbomConfigs, IFileSystemUtils fileSystemUtils, IPackageDetailsFactory packageDetailsFactory, ILicenseInformationFetcher licenseInformationFetcher, RuntimeConfiguration runtimeConfiguration = null)
: base(log, componentDetector, configuration, sbomConfigs, fileSystemUtils, packageDetailsFactory, licenseInformationFetcher, runtimeConfiguration)
{
}

Expand Down
6 changes: 0 additions & 6 deletions src/Microsoft.Sbom.Api/Microsoft.Sbom.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,11 @@
<PackageReference Include="Serilog.Extensions.Hosting" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Spectre.Console.Cli" />
<PackageReference Include="System.IO.FileSystem.AccessControl" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for the changes in this file?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I built locally, I got an error that some package references might not be needed, and it pointed here. I deleted it, and it still built, so it seems to have been right.

<PackageReference Include="System.Private.Uri" />
<PackageReference Include="System.Threading.Channels" />
<PackageReference Include="System.Threading.Tasks.Extensions" />
</ItemGroup>

<ItemGroup>
<!-- Pinned assemblies for transitive dependencies -->
<PackageReference Include="System.Net.Http" /> <!-- Used by ComponentDetection -->
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleToAttribute">
<_Parameter1>$(AssemblyName).Tests, PublicKey=$(StrongNameSigningPublicKey)</_Parameter1>
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.Sbom.Api/Workflows/SbomGenerationWorkflow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public virtual async Task<bool> RunAsync()
recorder.RecordTotalErrors(validErrors.ToList());
}

// Delete the generated _manifest folder if generation failed.
// Delete the generated _manifest folder if generation failed.
if (deleteSbomDir || validErrors.Any())
{
DeleteManifestFolder(sbomDir);
Expand Down Expand Up @@ -293,7 +293,7 @@ private void RemoveExistingManifestDirectory()
$"overwrite this folder.");
}

log.Warning(
log.Information(
$"Deleting pre-existing folder {rootManifestFolderPath} as {Constants.DeleteManifestDirBoolVariableName}" +
$" is 'true'.");
fileSystemUtils.DeleteDir(rootManifestFolderPath, true);
Expand Down
1 change: 0 additions & 1 deletion src/Microsoft.Sbom.Common/Microsoft.Sbom.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
<ItemGroup>
<PackageReference Include="Serilog" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="System.IO.FileSystem.AccessControl" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for the change in this file?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous

<PackageReference Include="System.Private.Uri" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public class RuntimeConfiguration
/// </summary>
public bool FollowSymlinks { get; set; } = true;

/// <summary>
/// Gets or sets a value indicating whether if set to true, we will not print a summary of the component governance to stdout.
/// </summary>
public bool NoComponentGovernanceSummary { get; set; } = false;

/// <summary>
/// Gets or sets a value of additional arguments for the component detector.
/// <para>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ namespace Microsoft.Sbom.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
/// <summary>
/// Initializes the services required to use the SBOM Tool using a default <see cref="InputConfiguration"/> and <see cref="LogEventLevel"/>.
/// </summary>
/// <param name="services">The services to which registrations are added.</param>
/// <param name="inputConfiguration">The default configuration to use, which will be registered as a singleton and will overwrite the currently-set <see cref="Configuration"/>.</param>
/// <param name="logLevel">The log verbosity level with which to use for the minimal logger used for internal services. These services require an <see cref="ILogger"/> but may themselves be used to <strong>initialize</strong> an <see cref="ILogger"/>, so need separate configuration.</param>
/// <remarks>The <see cref="InputConfiguration"/> is registered as a singleton in the container, and the <paramref name="logLevel"/> is used to create a default logger for internal utility classes, irrespective of any other <see cref="ILogger"/> registered in the container. This method does not register an instance of <see cref="ILogger"/>, however one is required for the system to function. Callers need to provide their own implementation.</remarks>
/// <returns></returns>
public static IServiceCollection AddSbomConfiguration(this IServiceCollection services, InputConfiguration inputConfiguration, LogEventLevel logLevel = LogEventLevel.Information)
{
ArgumentNullException.ThrowIfNull(inputConfiguration);
Expand All @@ -59,16 +67,17 @@ public static IServiceCollection AddSbomConfiguration(this IServiceCollection se
return services;
}

/// <summary>
/// Initializes the services required to use the SBOM Tool using a default <see cref="LogEventLevel"/>.
/// </summary>
/// <param name="services">The services to which registrations are added.</param>
/// <param name="logLevel">The log verbosity level with which to use for the minimal logger used for internal services. These services require an <see cref="ILogger"/> but may themselves be used to <strong>initialize</strong> an <see cref="ILogger"/>, so need separate configuration.</param>
/// <remarks>This method does not register an instance of <see cref="ILogger"/>, however one is required for the system to function. Callers need to provide their own implementation.</remarks>
public static IServiceCollection AddSbomTool(this IServiceCollection services, LogEventLevel logLevel = LogEventLevel.Information)
{
services
.AddSingleton<IConfiguration, Configuration>()
.AddTransient(_ => FileSystemUtilsProvider.CreateInstance(CreateLogger(logLevel)))
.AddTransient(x =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that this will create a breaking change for API users, as they will now have to inject a logger similarly to how we are now doing it in Program.cs, is that right? This is okay with me as our next release will be a breaking change anyways, but tagging @DaveTryon for visibility

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oof, good call - I didn't realize that the dependency injection project was public API surface area. Let me noodle on how that could be fixed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed a commit that minimizes the API surface area changes, and adds a ton of xmldoc that instructs users on the new requirement of registering their own ILogger. That won't stop someone that just updates and then runs tests from failing, but maybe that will help mitigate somewhat.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Forgind is there a way we can avoid making a breaking change here?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

baronfel didn't come up with a way, and he knows this part much better than I do. I can try to look around, but I have little hope of success.

{
logLevel = x.GetService<InputConfiguration>()?.Verbosity?.Value ?? logLevel;
return Log.Logger = CreateLogger(logLevel);
})
.AddTransient(x => FileSystemUtilsProvider.CreateInstance(CreateLogger(logLevel)))
.AddTransient<IWorkflow<SbomParserBasedValidationWorkflow>, SbomParserBasedValidationWorkflow>()
.AddTransient<IWorkflow<SbomGenerationWorkflow>, SbomGenerationWorkflow>()
.AddTransient<IWorkflow<SbomConsolidationWorkflow>, SbomConsolidationWorkflow>()
Expand Down Expand Up @@ -126,7 +135,8 @@ public static IServiceCollection AddSbomTool(this IServiceCollection services, L
.AddSingleton<IFileTypeUtils, FileTypeUtils>()
.AddSingleton<ISignValidationProvider, SignValidationProvider>()
.AddSingleton<IManifestParserProvider, ManifestParserProvider>()
.AddSingleton(x => {
.AddSingleton(x =>
{
var comparer = x.GetRequiredService<IOSUtils>().GetFileSystemStringComparer();
return new FileHashesDictionary(new ConcurrentDictionary<string, FileHashes>(comparer));
})
Expand Down Expand Up @@ -181,6 +191,11 @@ public static IServiceCollection AddSbomTool(this IServiceCollection services, L
return services;
}

/// <summary>
/// This method integrates Serilog into the <see cref="Microsoft.Extensions.Logging"/> infrastructure.
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection ConfigureLoggingProviders(this IServiceCollection services)
{
var providers = new LoggerProviderCollection();
Expand All @@ -201,7 +216,13 @@ public static IServiceCollection ConfigureLoggingProviders(this IServiceCollecti
return services;
}

private static ILogger CreateLogger(LogEventLevel logLevel)
/// <summary>
/// Creates a logger with the specified <paramref name="logLevel"/> that writes to a file and optionally to stderr.
/// This logger converts all errors from ComponentDetection assemblies to warnings, and does not write tabular output to stdout/stderr, only the configured file.
/// </summary>
/// <param name="logLevel"></param>
/// <returns></returns>
public static ILogger CreateLogger(LogEventLevel logLevel = LogEventLevel.Information)
{
return new RemapComponentDetectionErrorsToWarningsLogger(
new LoggerConfiguration()
Expand Down
Loading