Microsoft used to support a file called BuildInfo.config
that was useful for this exact sort of thing, but I think support for it was dropped after Visual Studio 2015.
Basically, it was a simple XML file that was created by MSBuild. When deployed alongside the app binaries, a logging framework could read the file and include the details in log output.
There are a bunch of ways you might create similar functionality yourself. Here's what I'd do...
- Create an empty build info file and check it in with your source code. This file is just a reference example--use placeholder data. It can be XML, JSON, or any other format (but should probably be text-based). Set the file's build action to "Content," so it will be included in the build output.
- Use your logging framework's template or telemetry initialization functionality to read the build info file and include its contents in log output. The exact implementation will depend on your logging framework.
- Add a Powershell script task to your Azure DevOps pipeline. The script should either update or overwrite the build info file. You can get some good info from build variables. The task should run before the MSBuild compilation step.
Create and Parse a BuildInfo file
For this example, I'm going to implement my build info file as simple key/value
pairs in a text file. I chose simple text because it allows a clear example
without requiring any third-party libraries or complex parsing. In your app, you
might want to use JSON, XML, or something else more standardized.
First, create a placeholder build info file. This file will be used in your
local dev environment, and will serve as a reference for your build info schema.
I called mine BuildInfo.txt
, and put it in my project's root directory.
COMMIT=commit not set
BUILD=build not set
Next, write a helper to parse the build info file. Mine's very rudimentary. It
would be wise to add some defenses against missing or malformed build info, but
I chose to omit any defensive code to keep the example focused.
class BuildInfo
{
// Singleton instance backing field
static readonly Lazy<BuildInfo> instance = new Lazy<BuildInfo>(() => new BuildInfo());
// Singleton instance public accessor
public static BuildInfo Instance => instance.Value;
public string Commit { get; }
public string Build { get; }
private BuildInfo()
{
// This is a very rudimentary example of parsing the info file. It
// will fail loudly on malformed input. Consider:
// 1) Using a standard file format (JSON, XML). I rolled my own
// here to avoid adding a dependency on a parsing library.
// 2) If you want your app to fail when no build info is
// available, add a more descriptive exception. If you don't
// want it to fail, add some devensive code or fallback logic.
var info = File.ReadAllLines("BuildInfo.txt")
.Select(l => l.Split('=', 2))
.ToDictionary(key => key[0], val => val[1]);
Commit = info["COMMIT"];
Build = info["BUILD"];
}
}
Add the Build Info to your Log Output
I'm using log4net here, but any logging framework should have some similar
mechanism to customise the log output. Consult the framework's documentation for
more info.
First, add your build info to the logging context. This should go somewhere in
your app's startup code--as early as possible.
log4net.GlobalContext.Properties["Build"] = BuildInfo.Instance.Build;
log4net.GlobalContext.Properties["Commit"] = BuildInfo.Instance.Commit;
Then, update your log appender's configuration to include the custom fields.
Here's an example configuration for the the console appender. The important bits
are %property{BuildId}
and %property{Commit}
.
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<target value="Console.Error" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%-5level [%property{BuildId}] [%property{Commit}] - %message%newline" />
</layout>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
Now, when you call log.Warn("Some log mesage")
, you'll see the following
console output:
WARN [build not set] [commit not set] - Some log mesage
Get Build Details from CI
Finally, you need to get the real build details from your CI environment. I did
this with a very simple PowerShell script task. Make sure the task runs before
your build step!
@(
"COMMIT=$($Env:BUILD_SOURCEVERSION)",
"BUILD=$($Env:BUILD_BUILDID)"
) | Out-File BuildInfo.txt
(Tip: You can see all of the environment variables available by running Get-ChildItem Env: | Sort Name
in a PowerShell task)
(Another tip: if you want to use JSON instead of text, take a look at the ConvertTo-Json
cmdlet)
Now, if all of the pieces are in place, the CI server should overwrite the
checked-in build info file. The new file should then be packages with your
deployable artifacts, and copied to your server. On startup, your app should
read the build info, and then the info should be included in every log message.
There are a lot of little things to get lined up between your build and deploy
process, so be prepared for some trial and error. CI/CD setup tends to be
tedious, in my experience.