When you use Team Foundation Server for Build Automation, you can configure build controller and agents. In the Team Foundation Administration Console, you can view the Logs however these logs provide minimal information. Even the Event Viewer provides minimal event logging rather than extensive logging. Now even if these Logs or Event Viewer provide you information that you require, there may be cases that you do not have complete access to the build server and this build server is managed by your organization’s support groups.
In this case, you can enable detailed level logs of build service so that you can read the logs in Read Only mode without having to login to the server. Note that this log file will have build information of all agents running on the controller and not specific to an agent. So if you are using a Shared Build Controller, everyone with access to this log file will have information about your builds too.
To get started, you need to manually create a configuration file with following content,
- <add name="BuildServiceTraceLevel" value="4"/>
- <trace autoflush="true" indentsize="4">
- <add name="myListener" type="Microsoft.TeamFoundation.TeamFoundationTextWriterTraceListener,Microsoft.TeamFoundation.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" initializeData="c:\Build\TFSBuildlogs\TFSBuildServiceHost.exe.log" />
- <remove name="Default" />
Name this file as TFSBuildServiceHost.exe.config
You can customize this configuration file by adding more trace listeners or even changing the location of build log file from C:\Build\TFSBuildlogs (create if it does not exist). If you are sharing this path with multiple teams, please make sure you only give Read Only rights to all parties.
As the name suggests, this is a configuration file for TFSBuildServiceHost.exe. On your build server, you can locate this executable at –
%programfiles%\Microsoft Team Foundation Server 2010\Tools
You can copy this file in the above path. If you are using Windows Server 2008/2012, it will validate your Administrative Privileges with UAC.
Ensure that no builds are in active state. Start the Command Prompt with ‘Run As Administrator’ privileges and execute these commands
net stop tfsbuildservicehost
net start tfsbuildservicehost
This will restart the TFSBuildServiceHost service and voila! The log files will detailed logging will be created at C:\Build\TFSBuildlogs
Note: This has been tested on TFS 2012 / 2013. I’ve not tested this on older versions of TFS.
If you don’t know what is CInject, I would recommend you to read this article and get the latest version of CInject from CodePlex website.
There are few injectors that are shipped with CInject
- LogInjector – Allows injecting logging in any .NET assembly/executable even if you do not have code of the assembly/executable. LogInjector is built on .NET 4.0 Client Profile and uses log4net for logging purpose
- PerformanceInjector – Allows logging the time required to execute injected method in the target assemblies
- ObjectValueInjector – Allows getting any property of arguments passed to the injected method
You can get started with directly using them without writing a single line of code.
Quick Guide to using CInject
So, to get started all you would require is to
- Download the latest stable version of CInject and unzip it to, lets say, C:\CInject
- Locate the assemblies (DLL) / executables (EXE) that need to be injected with along with their configuration files
- Locate your injectors assembly (DLL) and their configuration files.
- Open the downloaded CInject application [C\CInject\Cinject.exe]
- Choosing the Target Assembly
- If you are targeting a single assembly (to be injected), click on Assembly > Load Assembly (F3). As soon as you select the assembly, CInject will try to create a tree of classes and methods in the assembly.
- If you are targeting multiple assemblies (to be injected), click on Assembly > Select Directory (F2). CInject will browse through DLLs and EXEs in the directory and create a tree of classes and methods in each assembly or executable
- Choosing the Injector Assembly
- Auto Load – If you have copied the injector assemblies into CInject directory [here, C:\CInject], they will be automatically loaded in CInject
- Manually Load – Click on Assembly > Load Injector (F4). This will load the injectors
- Select all the classes/methods (in the blue panel) that you would want to inject. Then, select the LogInjector (in the yellow panel)
- You can click on ‘Add selected injectors to selected methods in target assembly’. This will add some entries in the grid (Assembly, Method, Injector). However, this does not mean that the assembly has been injected. If by mistake you have selected a wrong injector or a method, you can remove them by selecting it in the grid and clicking ‘Remove selected row’
- Select Inject > Run (F5) to inject and proceed ahead to inject the target assemblies.
- You will be prompted when the injection has been completed. With CInject 1.4, all the required files for each injector will be automatically copied to the folder of target assembly
- You can now execute the target assembly. This is the new assembly with selected injectors
You can alter the log configuration by editing LogInject.log4net.xml based on the Apache log4net configuration.
Currently, LogInjector supports DEBUG, INFO and ERROR modes
- Debug: Prints additional information such as calls to the destructor of the method, type of parameter to each method invoked
- Info: Just logs the name method invoked
- Error: Logs exceptions if LogInjector fails at any point
Apart from the method information, it logs Data-Time, Thread Id, and Assembly. You can configure these details in the xml file accompanied with the CInject executables.
In case you would want to use any other version of .NET, you would have to rebuild the code in Visual Studio.
PerformanceInjector also uses log4net for logging the performance of the methods. The configuration can be mentioned in loginject.log4net.xml file.
PerformanceInjector supports only INFO level of logging.
ObjectValueInjector uses ObjectSearch.xml to define which property needs to be retrieved in the arguments to the injected methods.
If the defined property (in configuration) does not exist in the argument, this injector does not throw any exception. If the defined property (in configuration) exist in the argument, injector will try to get the value of the property. If property value is NULL, it will log <null>
ObjectValueInjector uses DEBUG level of logging
If you don’t know what is CInject, I would recommend you to read this article and get the download latest version of CInject from CodePlex website.
Creating a Basic Injector
Once you have the latest executable, you can follow through these steps to create your own injector
Create a new Visual Studio Class Library Project (C# or VB.NET) and add a Reference to CInject.Injections.dll. You will find this assembly with the CInject application
Add a class called ‘MyInjector’ that derives from ICInject interface. This interface is part of CInject.Injections reference
The MyInjector class would look like
public class MyInjector : ICInject
public void OnComplete()
// This is called before exit of injected method
public void OnInvoke(CInject.Injections.Library.CInjection injection)
// Called at the entry of injected method
// To get the arguments passed to injected method
var arguments = injection.Arguments;
// To get value of property Text in any argument
var propertyValues = injection.GetPropertyValue("Text");
public void Dispose()
// dispose here if you want to
Compile the Visual Studio Project to create a DLL named MyInjector.dll and follow the steps mentioned in Using Injectors in CInject
Adding Configuration to your Injector
Adding configuration to an injector is similar to adding a configuration to any other project. You can choose from one of the following ways
- Rely on the configuration to be added in the configuration (app.config / web.config) of the target assembly / executable
- Add an independent configuration file (say, myconfig.xml) that needs to be copied in the target assembly/executable folder
- Add an independent configuration file and retrieve it from a Network Shared drive
I would not recommend Point -1 as it may cause conflicting configurations. To use step 2, you can use a very handy feature of CInject
Let’s say that your configuration file is myinjector-configuration.xml and it is added to the Visual Studio Project
You need to change the property of the configuration file to ‘Copy Always’ and decorate your injector class with DependentFiles attribute
public class MyInjector : ICInject
// Other code here
When CInject applies this injector to any assembly, it ensures that it copies myinjector-configuration.xml to root folder of target assembly / executable.
You can access this configuration file in code using standard .NET libraries
What if I some dependent / referenced assemblies as well?
Dependent / Referenced assemblies in your injector work same as configuration files.
You just have to mention them in the DependentFiles attribute and ensure that they are in the same directory as that of the injector.
While creating one of the Open Source projects, I started using log4net library for logging. But Apache haven’t released log4net for Microsoft .NET 4.0 so for using log4net in a Class Library, you need to add reference to System.Web
Well, would you really like to do that? No, of course not. The class library does not use any log4net AspNetTraceAppender they why reference System.Web? At least, my class library does not require AspNetTraceAppender! So we need a small patch to log4net to make it work with .NET 4.0 Client Profile
And if the consuming application decides to use the Class Library in a Web based project that requires AspNet tracing, they could use the replace the patched-log4net with the original-log4net
So here’s the way to apply the patch
- Download the log4net source code from Apache website
- Open & upgrade the solution in Visual Studio 2010 and the conversion would go through fine!
- Remove the System.Web project reference and add a reference to System.Configuration
- Unload the Test project from the solution since that’s not the
- Exclude Appender\AspNetTraceAppender.cs class from the project
- Comment the code in AssemblyInfo.cs
- Framework change:
- Navigate to Project -> log4net properties, and select the application tab
- Change the target framework to .NET Framework 4.0 Client Profile
- Change the Output Build Path to ..\build\bin\net\4.0\debug\
- Configuration: DEBUG
- Under Conditional compilation symbols, change this to NET;NET_1_0;NET_2_0;
- When the symbol NET_2_0 is added, log4net uses System.Net.Mail (instead of System.Web.Mail) which is deprecated but solve the purpose for us.
- Configuration: RELEASE
- Under Conditional compilation symbols change this to STRONG;NET;NET_1_0;NET_2_0;
- Edit the Project Properties > Signing
- Sign the assembly with a valid strong key
- Compile the project in Release mode and distribute the new assembly
Note: The strong key does not match with the original release.