diff --git a/src/Shared.CLI/BaseTool.cs b/src/Shared.CLI/BaseTool.cs
index c7a11589..020afee3 100644
--- a/src/Shared.CLI/BaseTool.cs
+++ b/src/Shared.CLI/BaseTool.cs
@@ -127,6 +127,72 @@ protected void SelectOutput(string outputFile)
}
}
+ protected void ConfigureLogging(BaseToolOptions options)
+ {
+ var config = NLog.LogManager.Configuration;
+ var rule = config.LoggingRules.FirstOrDefault();
+
+ if (rule != null)
+ {
+ NLog.LogLevel? targetLevel = ParseLogLevel(options.LogLevel);
+
+ if (targetLevel != null)
+ {
+ rule.SetLoggingLevels(targetLevel, NLog.LogLevel.Fatal);
+ NLog.LogManager.ReconfigExistingLoggers();
+ Logger.Debug("Log level set to: {0}", targetLevel);
+ }
+ else
+ {
+ Logger.Warn("Invalid log level '{0}'. Using default (Info). Valid values: Trace(0), Debug(1), Info(2), Warn(3), Error(4), Fatal(5), Off(6)", options.LogLevel);
+ }
+ }
+ }
+
+ ///
+ /// Parses a log level from either a string name or numeric value.
+ /// Supports NLog log levels: Trace, Debug, Info, Warn, Error, Fatal, Off (case-insensitive).
+ /// Also supports numeric values: 0=Trace, 1=Debug, 2=Info, 3=Warn, 4=Error, 5=Fatal, 6=Off.
+ ///
+ /// The log level as a string or number.
+ /// The corresponding NLog.LogLevel, or null if invalid.
+ private NLog.LogLevel? ParseLogLevel(string logLevel)
+ {
+ if (string.IsNullOrWhiteSpace(logLevel))
+ {
+ return null;
+ }
+
+ // Try parsing as a number first
+ if (int.TryParse(logLevel, out int numericLevel))
+ {
+ return numericLevel switch
+ {
+ 0 => NLog.LogLevel.Trace,
+ 1 => NLog.LogLevel.Debug,
+ 2 => NLog.LogLevel.Info,
+ 3 => NLog.LogLevel.Warn,
+ 4 => NLog.LogLevel.Error,
+ 5 => NLog.LogLevel.Fatal,
+ 6 => NLog.LogLevel.Off,
+ _ => null
+ };
+ }
+
+ // Try parsing as a string (case-insensitive)
+ return logLevel.ToLowerInvariant() switch
+ {
+ "trace" => NLog.LogLevel.Trace,
+ "debug" => NLog.LogLevel.Debug,
+ "info" => NLog.LogLevel.Info,
+ "warn" or "warning" => NLog.LogLevel.Warn,
+ "error" => NLog.LogLevel.Error,
+ "fatal" => NLog.LogLevel.Fatal,
+ "off" or "none" => NLog.LogLevel.Off,
+ _ => null
+ };
+ }
+
private bool redirectConsole = false;
}
}
diff --git a/src/Shared.CLI/Options/BaseToolOptions.cs b/src/Shared.CLI/Options/BaseToolOptions.cs
index fe9de91d..f1ca1554 100644
--- a/src/Shared.CLI/Options/BaseToolOptions.cs
+++ b/src/Shared.CLI/Options/BaseToolOptions.cs
@@ -8,4 +8,7 @@ namespace Microsoft.CST.OpenSource.OssGadget.Options;
public class BaseToolOptions
{
+ [Option('l', "log-level", Required = false, Default = "Info",
+ HelpText = "Set the logging level (Trace=0, Debug=1, Info=2, Warn=3, Error=4, Fatal=5, Off=6). Can use name or number.")]
+ public string LogLevel { get; set; } = "Info";
}
diff --git a/src/Shared.CLI/Tools/DownloadTool.cs b/src/Shared.CLI/Tools/DownloadTool.cs
index d42db957..2431d8ca 100644
--- a/src/Shared.CLI/Tools/DownloadTool.cs
+++ b/src/Shared.CLI/Tools/DownloadTool.cs
@@ -27,6 +27,8 @@ public DownloadTool(ProjectManagerFactory projectManagerFactory)
public override async Task RunAsync(DownloadToolOptions options)
{
+ ConfigureLogging(options);
+
if (options.Targets is IEnumerable targetList && targetList.Any())
{
foreach (string? target in targetList)
diff --git a/src/Shared/PackageManagers/BaseProjectManager.cs b/src/Shared/PackageManagers/BaseProjectManager.cs
index d9da3e53..b0975cba 100644
--- a/src/Shared/PackageManagers/BaseProjectManager.cs
+++ b/src/Shared/PackageManagers/BaseProjectManager.cs
@@ -475,6 +475,23 @@ public async Task> IdentifySourceRepositoryAsync(
///
protected static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
+ ///
+ /// Logs the download operation with package details and download URL.
+ ///
+ /// The PackageURL being downloaded.
+ /// The actual download URL.
+ protected static void LogDownload(PackageURL? purl, string? downloadUrl)
+ {
+ if (string.IsNullOrWhiteSpace(downloadUrl))
+ {
+ Logger.Debug("Downloading {0}...", purl?.ToString());
+ }
+ else
+ {
+ Logger.Debug("Downloading {0} from {1}", purl?.ToString(), downloadUrl);
+ }
+ }
+
///
/// Rank the source repo entry candidates by their edit distance.
///
diff --git a/src/Shared/PackageManagers/CPANProjectManager.cs b/src/Shared/PackageManagers/CPANProjectManager.cs
index 85b77d0a..e057e570 100644
--- a/src/Shared/PackageManagers/CPANProjectManager.cs
+++ b/src/Shared/PackageManagers/CPANProjectManager.cs
@@ -84,9 +84,10 @@ public override async Task> DownloadVersionAsync(PackageURL
string binaryUrl = root.GetProperty("download_url").GetString();
+ LogDownload(purl, binaryUrl);
+
HttpResponseMessage result = await httpClient.GetAsync(binaryUrl);
result.EnsureSuccessStatusCode();
- Logger.Debug("Downloading {0}...", purl);
string targetName = $"cpan-{packageName}@{packageVersion}";
string extractionPath = Path.Combine(TopLevelExtractionDirectory, targetName);
diff --git a/src/Shared/PackageManagers/CRANProjectManager.cs b/src/Shared/PackageManagers/CRANProjectManager.cs
index 98d24b5b..c022c2ca 100644
--- a/src/Shared/PackageManagers/CRANProjectManager.cs
+++ b/src/Shared/PackageManagers/CRANProjectManager.cs
@@ -61,7 +61,7 @@ public override async Task> DownloadVersionAsync(PackageURL
string url = $"{ENV_CRAN_ENDPOINT}/src/contrib/{packageName}_{packageVersion}.tar.gz";
System.Net.Http.HttpResponseMessage? result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
- Logger.Debug("Downloading {0}...", purl);
+ LogDownload(purl, url);
string targetName = $"cran-{packageName}@{packageVersion}";
string extractionPath = Path.Combine(TopLevelExtractionDirectory, targetName);
@@ -96,7 +96,7 @@ public override async Task> DownloadVersionAsync(PackageURL
string url = $"{ENV_CRAN_ENDPOINT}/src/contrib/Archive/{packageName}/{packageName}_{packageVersion}.tar.gz";
System.Net.Http.HttpResponseMessage? result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
- Logger.Debug("Downloading {0}...", purl);
+ LogDownload(purl, url);
string targetName = $"cran-{packageName}@{packageVersion}";
string extractionPath = Path.Combine(TopLevelExtractionDirectory, targetName);
diff --git a/src/Shared/PackageManagers/CargoProjectManager.cs b/src/Shared/PackageManagers/CargoProjectManager.cs
index f551bc98..0558f5d1 100644
--- a/src/Shared/PackageManagers/CargoProjectManager.cs
+++ b/src/Shared/PackageManagers/CargoProjectManager.cs
@@ -116,7 +116,7 @@ await retryPolicy.ExecuteAsync(async () =>
downloadedPaths.Add(extractionPath);
return;
}
- Logger.Debug("Downloading {0}", url);
+ LogDownload(purl, url.ToString());
HttpClient httpClient = CreateHttpClient();
diff --git a/src/Shared/PackageManagers/CocoapodsProjectManager.cs b/src/Shared/PackageManagers/CocoapodsProjectManager.cs
index 29093360..863e86c4 100644
--- a/src/Shared/PackageManagers/CocoapodsProjectManager.cs
+++ b/src/Shared/PackageManagers/CocoapodsProjectManager.cs
@@ -98,7 +98,8 @@ public override async Task> DownloadVersionAsync(PackageURL
if (url != null)
{
- Logger.Debug("Downloading {0}...", purl);
+ //Logger.Debug("Downloading {0}...", purl);
+ LogDownload(purl, url);
System.Net.Http.HttpResponseMessage result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
diff --git a/src/Shared/PackageManagers/ComposerProjectManager.cs b/src/Shared/PackageManagers/ComposerProjectManager.cs
index 5d13b157..014c44e2 100644
--- a/src/Shared/PackageManagers/ComposerProjectManager.cs
+++ b/src/Shared/PackageManagers/ComposerProjectManager.cs
@@ -70,7 +70,7 @@ public override async Task> DownloadVersionAsync(PackageURL
string? url = versionObject.Value.GetProperty("dist").GetProperty("url").GetString();
System.Net.Http.HttpResponseMessage? result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
- Logger.Debug("Downloading {0}...", purl);
+ LogDownload(purl, url);
string fsNamespace = OssUtilities.NormalizeStringForFileSystem(packageNamespace);
string fsName = OssUtilities.NormalizeStringForFileSystem(packageName);
diff --git a/src/Shared/PackageManagers/GemProjectManager.cs b/src/Shared/PackageManagers/GemProjectManager.cs
index 85a0ba0a..51b14cd9 100644
--- a/src/Shared/PackageManagers/GemProjectManager.cs
+++ b/src/Shared/PackageManagers/GemProjectManager.cs
@@ -61,7 +61,7 @@ public override async Task> DownloadVersionAsync(PackageURL
System.Net.Http.HttpResponseMessage result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
- Logger.Debug("Downloading {0}...", purl);
+ LogDownload(purl, url);
string targetName = $"rubygems-{packageName}@{packageVersion}";
string extractionPath = Path.Combine(TopLevelExtractionDirectory, targetName);
diff --git a/src/Shared/PackageManagers/GolangProjectManager.cs b/src/Shared/PackageManagers/GolangProjectManager.cs
index 7067adb9..b1ca943e 100644
--- a/src/Shared/PackageManagers/GolangProjectManager.cs
+++ b/src/Shared/PackageManagers/GolangProjectManager.cs
@@ -92,7 +92,7 @@ public override async Task> DownloadVersionAsync(PackageURL
System.Net.Http.HttpResponseMessage result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
- Logger.Debug("Downloading {0}...", purl);
+ LogDownload(purl, url.ToString());
string targetName = $"golang-{packageNamespace}-{packageName}-{packageSubpath}@{packageVersion}";
string extractionPath = Path.Combine(TopLevelExtractionDirectory, targetName);
diff --git a/src/Shared/PackageManagers/HackageProjectManager.cs b/src/Shared/PackageManagers/HackageProjectManager.cs
index 9eda543e..e1fc5399 100644
--- a/src/Shared/PackageManagers/HackageProjectManager.cs
+++ b/src/Shared/PackageManagers/HackageProjectManager.cs
@@ -62,7 +62,7 @@ public override async Task> DownloadVersionAsync(PackageURL
System.Net.Http.HttpResponseMessage result = await httpClient.GetAsync(url);
result.EnsureSuccessStatusCode();
- Logger.Debug("Downloading {0}...", purl.ToString());
+ LogDownload(purl, url);
string targetName = $"hackage-{packageName}@{packageVersion}";
string extractionPath = Path.Combine(TopLevelExtractionDirectory, targetName);
diff --git a/src/Shared/PackageManagers/MavenProjectManager.cs b/src/Shared/PackageManagers/MavenProjectManager.cs
index cf480235..064a309a 100644
--- a/src/Shared/PackageManagers/MavenProjectManager.cs
+++ b/src/Shared/PackageManagers/MavenProjectManager.cs
@@ -111,7 +111,8 @@ public override async Task> DownloadVersionAsync(PackageURL
System.Net.Http.HttpResponseMessage result = await httpClient.GetAsync(artifact.Uri);
result.EnsureSuccessStatusCode();
- Logger.Debug($"Downloading {purl}...");
+ LogDownload(purl, artifact.Uri.ToString());
+
string targetName = $"maven-{packageNamespace}-{packageName}{artifact.Type}@{packageVersion}";
targetName = targetName.Replace('/', '-');
diff --git a/src/Shared/PackageManagers/NPMProjectManager.cs b/src/Shared/PackageManagers/NPMProjectManager.cs
index 520d2e24..0e8c74b4 100644
--- a/src/Shared/PackageManagers/NPMProjectManager.cs
+++ b/src/Shared/PackageManagers/NPMProjectManager.cs
@@ -111,7 +111,8 @@ public override async Task> DownloadVersionAsync(PackageURL
string? tarball = doc.RootElement.GetProperty("versions").GetProperty(packageVersion).GetProperty("dist").GetProperty("tarball").GetString();
HttpResponseMessage result = await httpClient.GetAsync(tarball);
result.EnsureSuccessStatusCode();
- Logger.Debug("Downloading {0}...", purl?.ToString());
+ LogDownload(purl, tarball);
+ //Logger.Debug("Downloading {0}...", purl?.ToString());
string targetName = $"npm-{packageName}@{packageVersion}";
string extractionPath = Path.Combine(TopLevelExtractionDirectory, targetName);
if (doExtract && Directory.Exists(extractionPath) && cached == true)
diff --git a/src/Shared/PackageManagers/PyPIProjectManager.cs b/src/Shared/PackageManagers/PyPIProjectManager.cs
index 4178b447..e008b2a3 100644
--- a/src/Shared/PackageManagers/PyPIProjectManager.cs
+++ b/src/Shared/PackageManagers/PyPIProjectManager.cs
@@ -142,7 +142,10 @@ public override async Task> DownloadVersionAsync(PackageURL
continue; // Missing a package type
}
- System.Net.Http.HttpResponseMessage result = await httpClient.GetAsync(release.GetProperty("url").GetString());
+ string? downloadUrl = release.GetProperty("url").GetString();
+ LogDownload(purl, downloadUrl);
+
+ System.Net.Http.HttpResponseMessage result = await httpClient.GetAsync(downloadUrl);
result.EnsureSuccessStatusCode();
string targetName = $"pypi-{packageType}-{packageName}@{packageVersion}";
string extension = ".tar.gz";
diff --git a/src/Shared/PackageManagers/VSMProjectManager.cs b/src/Shared/PackageManagers/VSMProjectManager.cs
index 96638244..0df72170 100644
--- a/src/Shared/PackageManagers/VSMProjectManager.cs
+++ b/src/Shared/PackageManagers/VSMProjectManager.cs
@@ -134,7 +134,10 @@ public override async Task> DownloadVersionAsync(PackageURL
try
{
- HttpResponseMessage downloadResult = await httpClient.GetAsync(source.GetString());
+ string? downloadUrl = source.GetString();
+ LogDownload(purl, downloadUrl);
+
+ HttpResponseMessage downloadResult = await httpClient.GetAsync(downloadUrl);
downloadResult.EnsureSuccessStatusCode();
string? targetName = $"vsm-{packageName}@{packageVersion}-{assetType}";
string extractionPath = Path.Combine(TopLevelExtractionDirectory, targetName);