Skip to content

Check DOTNET_HOST_* for host (COREHOST_*) tracing environment variables #117894

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

Merged
merged 4 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ private void OldHost_LatestRuntime_ForwardCompatible(TestApp previousVersionApp)
// 2) App rolls forward to newer runtime
File.Copy(previousVersionApp.AppExe, appExe, true);
Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.CaptureStdOut().CaptureStdErr()
.EnvironmentVariable("COREHOST_TRACE", "1") // Old host, so we need to use the old variable name
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
Expand All @@ -95,7 +96,8 @@ private void OldHost_LatestRuntime_ForwardCompatible(TestApp previousVersionApp)
{
File.Copy(previousVersionApp.HostFxrDll, app.HostFxrDll, true);
Command.Create(appExe)
.EnableTracingAndCaptureOutputs()
.CaptureStdOut().CaptureStdErr()
.EnvironmentVariable("COREHOST_TRACE", "1") // Old host, so we need to use the old variable name
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
Expand Down
62 changes: 22 additions & 40 deletions src/installer/tests/HostActivation.Tests/Tracing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,18 @@

namespace Microsoft.DotNet.CoreSetup.Test.HostActivation
{
public class Tracing : IClassFixture<Tracing.SharedTestState>
public class Tracing
{
private SharedTestState sharedTestState;
// Trace messages currently expected for running dotnet --list-runtimes
private const string ExpectedVerboseMessage = "--- Executing in muxer mode";
private const string ExpectedInfoMessage = "--- Invoked dotnet";

// Trace messages currently expected for a passing app (somewhat randomly selected)
private const string ExpectedVerboseMessage = "--- Begin breadcrumb write";
private const string ExpectedInfoMessage = "Deps file:";
private const string ExpectedBadPathMessage = "Unable to open COREHOST_TRACEFILE=";

public Tracing(Tracing.SharedTestState fixture)
{
sharedTestState = fixture;
}
private const string ExpectedBadPathMessage = "Unable to open specified trace file for writing:";

[Fact]
public void TracingOff()
{
TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll)
TestContext.BuiltDotNet.Exec("--list-runtimes")
.CaptureStdOut()
.CaptureStdErr()
.Execute()
Expand All @@ -36,66 +30,60 @@ public void TracingOff()
[Fact]
public void TracingOnDefault()
{
TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll)
TestContext.BuiltDotNet.Exec("--list-runtimes")
.EnableTracingAndCaptureOutputs()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
.And.HaveStdErrContaining(ExpectedInfoMessage)
.And.HaveStdErrContaining(ExpectedVerboseMessage);
}

[Fact]
public void TracingOnVerbose()
{
TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll)
TestContext.BuiltDotNet.Exec("--list-runtimes")
.EnableTracingAndCaptureOutputs()
.EnvironmentVariable(Constants.HostTracing.VerbosityEnvironmentVariable, "4")
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
.And.HaveStdErrContaining(ExpectedInfoMessage)
.And.HaveStdErrContaining(ExpectedVerboseMessage);
}

[Fact]
public void TracingOnInfo()
{
TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll)
TestContext.BuiltDotNet.Exec("--list-runtimes")
.EnableTracingAndCaptureOutputs()
.EnvironmentVariable(Constants.HostTracing.VerbosityEnvironmentVariable, "3")
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
.And.HaveStdErrContaining(ExpectedInfoMessage)
.And.NotHaveStdErrContaining(ExpectedVerboseMessage);
}

[Fact]
public void TracingOnWarning()
{
TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll)
TestContext.BuiltDotNet.Exec("--list-runtimes")
.EnableTracingAndCaptureOutputs()
.EnvironmentVariable(Constants.HostTracing.VerbosityEnvironmentVariable, "2")
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
.And.NotHaveStdErrContaining(ExpectedInfoMessage)
.And.NotHaveStdErrContaining(ExpectedVerboseMessage);
}

[Fact]
public void TracingOnToFileDefault()
{

string traceFilePath;
TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll)
TestContext.BuiltDotNet.Exec("--list-runtimes")
.EnableHostTracingToFile(out traceFilePath)
.CaptureStdOut()
.CaptureStdErr()
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
.And.NotHaveStdErrContaining(ExpectedInfoMessage)
.And.NotHaveStdErrContaining(ExpectedVerboseMessage)
.And.FileExists(traceFilePath)
Expand All @@ -107,12 +95,11 @@ public void TracingOnToFileDefault()
[Fact]
public void TracingOnToFileBadPathDefault()
{
TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll)
TestContext.BuiltDotNet.Exec("--list-runtimes")
.EnableTracingAndCaptureOutputs()
.EnvironmentVariable(Constants.HostTracing.TraceFileEnvironmentVariable, "badpath/TracingOnToFileBadPathDefault.log")
.Execute()
.Should().Pass()
.And.HaveStdOutContaining("Hello World")
.And.HaveStdErrContaining(ExpectedInfoMessage)
.And.HaveStdErrContaining(ExpectedVerboseMessage)
.And.HaveStdErrContaining(ExpectedBadPathMessage);
Expand All @@ -123,36 +110,31 @@ public void TracingOnToDirectory()
{
using (TestArtifact directory = TestArtifact.Create("trace"))
{
var result = TestContext.BuiltDotNet.Exec(sharedTestState.App.AppDll)
var result = TestContext.BuiltDotNet.Exec("--list-runtimes")
.EnableHostTracingToPath(directory.Location)
.CaptureStdOut()
.CaptureStdErr()
.Execute();

string traceFilePath = Path.Combine(directory.Location, $"{Path.GetFileNameWithoutExtension(Binaries.DotNet.FileName)}.{result.ProcessId}.log");
result.Should().Pass()
.And.HaveStdOutContaining("Hello World")
.And.NotHaveStdErrContaining(ExpectedInfoMessage)
.And.NotHaveStdErrContaining(ExpectedVerboseMessage)
.And.FileExists(traceFilePath)
.And.FileContains(traceFilePath, ExpectedVerboseMessage);
}
}

public class SharedTestState : IDisposable
[Fact]
public void LegacyVariableName()
{
public TestApp App { get; }

public SharedTestState()
{
App = TestApp.CreateFromBuiltAssets("HelloWorld");
App.CreateAppHost();
}

public void Dispose()
{
App?.Dispose();
}
TestContext.BuiltDotNet.Exec("--list-runtimes")
.EnvironmentVariable("COREHOST_TRACE", "1")
.CaptureStdErr()
.Execute()
.Should().Pass()
.And.HaveStdErrContaining(ExpectedInfoMessage)
.And.HaveStdErrContaining(ExpectedVerboseMessage);
}
}
}
6 changes: 3 additions & 3 deletions src/installer/tests/TestUtils/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@ public static class MultilevelLookup

public static class HostTracing
{
public const string TraceLevelEnvironmentVariable = "COREHOST_TRACE";
public const string TraceFileEnvironmentVariable = "COREHOST_TRACEFILE";
public const string VerbosityEnvironmentVariable = "COREHOST_TRACE_VERBOSITY";
public const string TraceLevelEnvironmentVariable = "DOTNET_HOST_TRACE";
public const string TraceFileEnvironmentVariable = "DOTNET_HOST_TRACEFILE";
public const string VerbosityEnvironmentVariable = "DOTNET_HOST_TRACE_VERBOSITY";
}

public static class DotnetRoot
Expand Down
41 changes: 30 additions & 11 deletions src/native/corehost/hostmisc/trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
#define TRACE_VERBOSITY_INFO 3
#define TRACE_VERBOSITY_VERBOSE 4

// g_trace_verbosity is used to encode COREHOST_TRACE and COREHOST_TRACE_VERBOSITY to selectively control output of
// g_trace_verbosity is used to encode DOTNET_HOST_TRACE and DOTNET_HOST_TRACE_VERBOSITY to selectively control output of
// trace::warn(), trace::info(), and trace::verbose()
// COREHOST_TRACE=0 COREHOST_TRACE_VERBOSITY=N/A implies g_trace_verbosity = 0. // Trace "disabled". error() messages will be produced.
// COREHOST_TRACE=1 COREHOST_TRACE_VERBOSITY=4 or unset implies g_trace_verbosity = 4. // Trace "enabled". verbose(), info(), warn() and error() messages will be produced
// COREHOST_TRACE=1 COREHOST_TRACE_VERBOSITY=3 implies g_trace_verbosity = 3. // Trace "enabled". info(), warn() and error() messages will be produced
// COREHOST_TRACE=1 COREHOST_TRACE_VERBOSITY=2 implies g_trace_verbosity = 2. // Trace "enabled". warn() and error() messages will be produced
// COREHOST_TRACE=1 COREHOST_TRACE_VERBOSITY=1 implies g_trace_verbosity = 1. // Trace "enabled". error() messages will be produced
// DOTNET_HOST_TRACE=0 DOTNET_HOST_TRACE_VERBOSITY=N/A implies g_trace_verbosity = 0. // Trace "disabled". error() messages will be produced.
// DOTNET_HOST_TRACE=1 DOTNET_HOST_TRACE_VERBOSITY=4 or unset implies g_trace_verbosity = 4. // Trace "enabled". verbose(), info(), warn() and error() messages will be produced
// DOTNET_HOST_TRACE=1 DOTNET_HOST_TRACE_VERBOSITY=3 implies g_trace_verbosity = 3. // Trace "enabled". info(), warn() and error() messages will be produced
// DOTNET_HOST_TRACE=1 DOTNET_HOST_TRACE_VERBOSITY=2 implies g_trace_verbosity = 2. // Trace "enabled". warn() and error() messages will be produced
// DOTNET_HOST_TRACE=1 DOTNET_HOST_TRACE_VERBOSITY=1 implies g_trace_verbosity = 1. // Trace "enabled". error() messages will be produced
static int g_trace_verbosity = 0;
static FILE * g_trace_file = nullptr;
thread_local static trace::error_writer_fn g_error_writer = nullptr;
Expand Down Expand Up @@ -52,16 +52,35 @@ namespace
};

spin_lock g_trace_lock;

template<size_t NameLen>
bool get_host_env_var(const pal::char_t (&name)[NameLen], pal::string_t* value)
{
assert(name[NameLen - 1] == _X('\0')); // name is expected to be null-terminated

// DOTNET_HOST_* takes precedence
constexpr size_t dotnet_host_prefix_len = STRING_LENGTH("DOTNET_HOST_");
pal::char_t dotnet_host_name[dotnet_host_prefix_len + NameLen];
pal::snwprintf(dotnet_host_name, ARRAY_SIZE(dotnet_host_name), _X("DOTNET_HOST_%s"), name);
if (pal::getenv(dotnet_host_name, value))
return true;

// COREHOST_* for backwards compatibility
constexpr size_t corehost_prefix_len = STRING_LENGTH("COREHOST_");
pal::char_t corehost_name[corehost_prefix_len + NameLen];
pal::snwprintf(corehost_name, ARRAY_SIZE(corehost_name), _X("COREHOST_%s"), name);
return pal::getenv(corehost_name, value);
}
}

//
// Turn on tracing for the corehost based on "COREHOST_TRACE" & "COREHOST_TRACEFILE" env.
// Turn on tracing for the corehost based on "DOTNET_HOST_TRACE" & "DOTNET_HOST_TRACEFILE" env.
//
void trace::setup()
{
// Read trace environment variable
pal::string_t trace_str;
if (!pal::getenv(_X("COREHOST_TRACE"), &trace_str))
if (!get_host_env_var(_X("TRACE"), &trace_str))
{
return;
}
Expand Down Expand Up @@ -91,7 +110,7 @@ bool trace::enable()
std::lock_guard<spin_lock> lock(g_trace_lock);

g_trace_file = stderr; // Trace to stderr by default
if (pal::getenv(_X("COREHOST_TRACEFILE"), &tracefile_str))
if (get_host_env_var(_X("TRACEFILE"), &tracefile_str))
{
if (pal::is_directory(tracefile_str))
{
Expand Down Expand Up @@ -122,7 +141,7 @@ bool trace::enable()
}

pal::string_t trace_str;
if (!pal::getenv(_X("COREHOST_TRACE_VERBOSITY"), &trace_str))
if (!get_host_env_var(_X("TRACE_VERBOSITY"), &trace_str))
{
g_trace_verbosity = TRACE_VERBOSITY_VERBOSE; // Verbose trace by default
}
Expand All @@ -134,7 +153,7 @@ bool trace::enable()

if (file_open_error)
{
trace::error(_X("Unable to open COREHOST_TRACEFILE=%s for writing"), tracefile_str.c_str());
trace::error(_X("Unable to open specified trace file for writing: %s"), tracefile_str.c_str());
}
return true;
}
Expand Down
2 changes: 1 addition & 1 deletion src/native/corehost/hostpolicy/hostpolicy_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ int hostpolicy_context_t::initialize(const hostpolicy_init_t &hostpolicy_init, c
// otherwise fail early.
if (!bundle::info_t::is_single_file_bundle())
{
trace::error(_X("Could not resolve CoreCLR path. For more details, enable tracing by setting COREHOST_TRACE environment variable to 1"));
trace::error(_X("Could not resolve CoreCLR path. For more details, enable tracing by setting DOTNET_HOST_TRACE environment variable to 1"));
return StatusCode::CoreClrResolveFailure;
}

Expand Down
Loading