MSBuild
logging
development
variable output
build automation

How to output a variable value to the log from MSBuild

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When an MSBuild property or item has an unexpected value, the fastest debugging step is usually to print it in the build log. The important detail is that MSBuild values are evaluated at different phases, so logging the right value depends on where the Message task runs and how much verbosity the build command shows.

Log Properties with the Message Task

For ordinary property values, add a target and emit them through Message.

xml
1<Project Sdk="Microsoft.NET.Sdk">
2  <PropertyGroup>
3    <Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
4    <ApiBaseUrl>https://internal.example</ApiBaseUrl>
5  </PropertyGroup>
6
7  <Target Name="LogBuildProperties" BeforeTargets="Build">
8    <Message Importance="High" Text="Configuration=$(Configuration)" />
9    <Message Importance="High" Text="ApiBaseUrl=$(ApiBaseUrl)" />
10  </Target>
11</Project>

Placing the messages in a target such as BeforeTargets="Build" helps ensure you see values closer to actual execution time, after imports and conditional assignments have taken effect.

Understand Importance and Verbosity

A correct Message task can still appear to do nothing if the build verbosity is too low. MSBuild filters message output by both message importance and command verbosity.

bash
msbuild MyProject.csproj /t:Build /v:minimal
msbuild MyProject.csproj /t:Build /v:detailed
msbuild MyProject.csproj /t:Build /v:diagnostic

High importance is the safest choice when you need to guarantee visibility. Lower importance settings may only appear with detailed or diagnostic logging.

Log Items and Metadata

Many build problems come from item groups rather than from single properties. In that case, print the item identity or metadata.

xml
1<ItemGroup>
2  <ContentFiles Include="Assets\**\*.*" />
3</ItemGroup>
4
5<Target Name="LogContentFiles" BeforeTargets="Build">
6  <Message Importance="High" Text="Content file: %(ContentFiles.Identity)" />
7</Target>

This is useful for confirming globbing rules, includes, excludes, and generated item lists.

Gate Diagnostic Output Behind a Property

Permanent verbose logging creates noise in normal builds. A better pattern is to enable debugging only when requested.

xml
1<PropertyGroup>
2  <DebugBuildContext>false</DebugBuildContext>
3</PropertyGroup>
4
5<Target Name="DebugBuildContext" BeforeTargets="Build"
6        Condition="'$(DebugBuildContext)' == 'true'">
7  <Message Importance="High" Text="Project=$(MSBuildProjectName)" />
8  <Message Importance="High" Text="TargetFramework=$(TargetFramework)" />
9  <Message Importance="High" Text="OutputPath=$(OutputPath)" />
10</Target>

Then enable it only for a troubleshooting run:

bash
msbuild MyProject.csproj /p:DebugBuildContext=true /v:detailed

This keeps routine builds clean while still giving you a repeatable investigation path.

Use Binary Logs for Hard Cases

If values are changing across imports, targets, or SDK logic, plain text messages may not be enough. In that case, generate a binary log.

bash
msbuild MySolution.sln /t:Build /v:diagnostic /bl:build.binlog

A binary log lets you inspect property evaluation, target order, and task execution in far more detail than a few temporary messages. It is often the best option when a property seems to change "by itself" between phases.

Avoid Logging Secrets

Be careful with tokens, passwords, or signing material. Build logs often end up in CI artifacts where many people can read them. For sensitive values, log only whether the property is present.

xml
<Message Importance="High"
         Text="ApiToken configured: $([System.String]::IsNullOrEmpty('$(ApiToken)') == false)" />

That gives enough diagnostic signal without leaking the value itself.

Common Pitfalls

One common problem is logging too early, before later imports or targets override the value you care about. If that happens, move the logging target closer to the build phase you are diagnosing.

Another issue is using Importance="Low" and then running with minimal verbosity. The message exists, but the log hides it.

Teams also tend to scatter temporary Message tasks across many project files. It is usually better to keep a small, reusable diagnostic target in a shared build file so the debugging approach stays consistent.

Summary

  • Use the Message task to print property and item values from a target.
  • Place the target near the build phase whose values you want to inspect.
  • Match message importance with the verbosity level you are using.
  • Gate optional diagnostics behind a property so normal builds stay readable.
  • Prefer binary logs for complex evaluation problems and avoid logging secrets directly.

Course illustration
Course illustration

All Rights Reserved.