Updated on
NLog is how an ASP.NET Core Web API writes its logging to a file. One package and one line in Program.cs, and the ILogger<T> calls the code already makes land in a file we configure.
ASP.NET Core ships logging in the box. We inject ILogger<T> into a class, call it, and the messages go wherever the registered providers send them: the console by default, and nowhere durable. NLog is the provider that writes files, and this part wires it into the Web API we started in part 2.
If you want to see all the basic instructions and complete navigation for this series, please follow the following link: Introduction page for this tutorial.
For the previous part check out: Service Configuration in ASP.NET Core Web API With Extension Methods
How Does Logging Work in ASP.NET Core?
Logging in ASP.NET Core is one API in front of many destinations. We inject ILogger<T> where we need it, call a method with a level, and every registered provider decides what to do with the message.
The type parameter is not decoration. It sets the message’s category, which is how output is filtered per class from configuration without touching code.
Providers are the destinations. The console provider is registered by default; a file, a database, or a hosted service each arrive as another provider, and adding one changes nothing at the call sites.
Levels rank severity from trace to critical, and configuration decides which of them survive in which environment. Development usually keeps debug and above, production usually starts at information or warning.
Messages are templates rather than sentences. Writing the placeholders as names and passing the values separately lets a destination store them as fields, which is what makes logs searchable later.
ILogger method | LogLevel | NLog level | Use it for |
|---|---|---|---|
LogTrace | Trace | Trace | The most detailed diagnostics, off outside development |
LogDebug | Debug | Debug | Values and flow while developing |
LogInformation | Information | Info | Normal events worth a record: a request served, a job finished |
LogWarning | Warning | Warn | Something unexpected that did not stop the operation |
LogError | Error | Error | An operation failed and the caller was affected |
LogCritical | Critical | Fatal | The application cannot continue |
Three interfaces carry the whole model, and our article on ILogger, ILoggerFactory and ILoggerProvider takes each of them apart. For this part, the one that matters is ILogger<T>, because it is the only one we inject.
The template form is worth adopting from the first call rather than retrofitting later. Writing message templates rather than interpolated strings is what lets a destination keep the values as fields instead of flattening them into a sentence, and no provider can recover a value that was interpolated away before it arrived.
How Do We Add NLog to an ASP.NET Core Web API?
Two steps. Install the NLog package for ASP.NET Core, then tell the host to use it.
The package to install is the ASP.NET Core one rather than the base library. It brings NLog itself along and adds the layout renderers that know about requests, which is most of the reason to use NLog in a web application at all.
The wiring is one line in Program.cs. From that point ILogger<T> messages reach NLog alongside the providers already registered, and we choose whether to clear those first.
NLog reads its configuration from nlog.config in the project folder, and that file is where every decision about destinations, file names, and line format lives. None of it appears in C#.
Nothing at the call sites changes. Code that logged before this step logs the same way after it, and now the messages land in a file.
The package is NLog.Web.AspNetCore, and one command installs it into the project that needs it:
dotnet add package NLog.Web.AspNetCore
It pulls in NLog and NLog.Extensions.Logging as transitive dependencies, so this single reference is the whole installation. In the sample it sits on the LoggerService project.
The wiring is two lines in Program.cs, and only the second of them is about NLog:
using NLog.Web; var builder = WebApplication.CreateBuilder(args); builder.Logging.ClearProviders(); builder.Host.UseNLog();
There is nothing to load by hand. NLog finds nlog.config in the application’s output directory on its own, so no call has to name the file or build a path to it. The file gets there without a project-file entry as well, because the Web SDK copies every .config file in the project to the build output, which is why the sample’s .csproj never mentions it.
ClearProviders() is a decision about duplication, not about whether NLog works. A default host registers four providers before we add anything, so without that line every message goes to the file and to the console and to the debug and event sinks as well. With it, NLog is the only destination left.
How Do We Configure nlog.config?
nlog.config is an XML file with two parts that matter: targets and rules.
A target is a destination. The file target here writes to disk, and its fileName decides both where the file lives and how it rolls: putting ${shortdate} in the name gives one file per day with no other configuration.
A rule connects loggers to targets. The sample’s single rule sends everything at debug level and above to the file target, matching every category with *.
The layout attribute decides what one line looks like. It is a string of renderers, and it is where the timestamp, the level, the message, and the exception are placed in the order we want them.
Relative paths are resolved from the application’s base directory, so the file lands beside the built application rather than at an absolute location we have to invent.
| Renderer | Produces |
|---|---|
${longdate} | Full date and time |
${shortdate} | Date only, which is what makes one file per day |
${level} | The level name, with uppercase=true to match the sample |
${message} | The rendered message |
${exception} | The exception, with format=ToString for the stack trace |
${logger} | The category, which is the class name when using ILogger<T> |
${aspnet-request-url} | The request URL, which needs NLog.Web.AspNetCore |
${aspnet-mvc-action} | The action name, which needs NLog.Web.AspNetCore |
The last two rows need one extra line in the file. Layout renderers that read the current request ship in NLog.Web.AspNetCore rather than in NLog itself, and NLog registers them only when the assembly is declared: <extensions><add assembly="NLog.Web.AspNetCore"/></extensions>. Copy an ${aspnet-request-url} without it and the configuration fails to parse rather than rendering an empty value.
The sample’s file is deliberately small, and every attribute in it is one of the four ideas above:
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true">
<targets>
<target name="logfile" xsi:type="File"
fileName="logs/${shortdate}_logfile.txt"
layout="${longdate} ${level:uppercase=true} ${message}"/>
</targets>
<rules>
<logger name="*" minlevel="Debug" writeTo="logfile" />
</rules>
</nlog>
The fileName is relative, which is the whole reason this sample runs on any machine. An absolute path pinned to one developer’s drive is the classic way this file goes wrong, and the failure is silent: NLog discards the message rather than raising anything, so the application runs perfectly and the file never appears.
autoReload="true" means NLog watches the file and picks up an edit while the application is running, which is what makes changing a level or a layout a one-file operation instead of a restart.
A file is not the only target worth knowing. The same configuration shape sends the same messages to a database if we point a target at one, which our article on sending the same messages to SQL Server instead covers end to end. And if structured output is the priority rather than a file on disk, Serilog behind the same ILogger API is the provider most teams reach for.
How Do We Write Log Messages From a Controller?
We ask for an ILogger<T> in the constructor and call it. There is nothing to register: the container already knows how to build a logger for any type, so the controller states what it needs and receives it.
The type argument is the class doing the logging, which is what gives every message from this controller its own category:
[ApiController]
[Route("[controller]")]
public class WeatherForecastWithILoggerController : ControllerBase
{
private readonly ILogger<WeatherForecastWithILoggerController> _logger;
public WeatherForecastWithILoggerController(ILogger<WeatherForecastWithILoggerController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<string> Get()
{
_logger.LogInformation("Serving {Count} forecast values", 2);
return ["value1", "value2"];
}
}
{Count} is a placeholder rather than a variable, and the value travels beside the template rather than inside it. A file target renders it into the sentence; a structured target keeps it as a field we can query on.
Starting the application and calling /weatherforecast produces the line in logs/, beside the built application rather than beside the project file. That folder is created on the first write, so it does not exist until a message is actually logged.
The sample’s controller injects ILoggerManager instead, which is the wrapper the next section builds and argues for. Both reach the same file, because NLog is registered as a provider for one and used directly by the other.
Should We Write a Custom Logger Wrapper?
Usually not, and the reason is worth stating before the code.
ILogger<T> is already an abstraction. Wrapping it in our own interface adds a second one, and the second one is narrower than the first: a wrapper that takes a string per level cannot carry message templates, scopes, event IDs, or an exception argument.
The category suffers too. ILogger<T> names each message after the class that logged it, which is what per-class filtering in configuration matches on. A single shared wrapper class names every message after itself.
There is one honest argument for it. A project that expects to change its logging approach across many files gains one seam to change instead of many, and that is the argument this series is making.
We build it here for that reason, and we keep it deliberately thin, so nothing below depends on it that could not use ILogger<T> directly.
The wrapper is deliberately thin enough that the library behind it is a detail: the current editions of our Ultimate ASP.NET Core Web API course keep this same ILoggerManager and put Serilog behind it instead of NLog, and nothing above the interface changes when they do.
The usual second argument, testability, does not survive contact with the platform: testing code that logs, without a wrapper is a solved problem, because FakeLogger ships for exactly that purpose.
The wrapper lives in two class libraries. Contracts holds the interface and LoggerService holds the implementation, so the web project references LoggerService, which references Contracts. Splitting them this way is what lets the later parts of the series depend on the contract without depending on the logging library.
The interface is four methods, one per level the series uses:
namespace Contracts
{
public interface ILoggerManager
{
void LogInfo(string message);
void LogWarn(string message);
void LogDebug(string message);
void LogError(string message);
}
}
Four is the deliberate cost of this design, and it is worth naming: the table above lists six levels, so a codebase that only ever calls this interface has no way to write a trace or a critical message.
The implementation forwards each call to a static NLog logger:
using Contracts;
using NLog;
namespace LoggerService
{
public class LoggerManager : ILoggerManager
{
private static readonly ILogger Logger = LogManager.GetCurrentClassLogger();
public void LogDebug(string message) => Logger.Debug(message);
public void LogError(string message) => Logger.Error(message);
public void LogInfo(string message) => Logger.Info(message);
public void LogWarn(string message) => Logger.Warn(message);
}
}
GetCurrentClassLogger() names the logger after the class it is called in, and here that class is LoggerManager. Every message written through this wrapper is therefore categorised as LoggerService.LoggerManager, whichever controller or service produced it, which is the concrete form of the cost this section opened with.
That is a real cost, not a stylistic one. Microsoft’s logging documentation states that “The recommended practice for choosing a log category name is to use the fully qualified name of the class that’s creating the log message”, which is exactly what the wrapper takes away.
Unlike ILogger<T>, the wrapper is ours, so the container has to be told about it. The registration goes in the ServiceExtensions class we built in part 2:
public static void ConfigureLoggerService(this IServiceCollection services)
{
services.AddSingleton<ILoggerManager, LoggerManager>();
}
Then we invoke it in Program.cs, right above builder.Services.AddControllers(): builder.Services.ConfigureLoggerService();
A singleton is the right lifetime here because the wrapper holds no per-request state. It is one object for the application, handing every call to the same NLog logger.
With that in place, a controller can take the wrapper instead of ILogger<T>, which is what the sample’s WeatherForecastController does:
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly ILoggerManager _logger;
public WeatherForecastController(ILoggerManager logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<string> Get()
{
_logger.LogInfo("Here is info message from the controller.");
_logger.LogDebug("Here is debug message from the controller.");
_logger.LogWarn("Here is warn message from the controller.");
_logger.LogError("Here is error message from the controller.");
return ["value1", "value2"];
}
}
One request writes four lines to the day’s file, one per level. That is the seam the rest of the series uses: parts 4, 5 and 6 inject this same interface, and none of them has to know which library is underneath it.
What Are Dependency Injection and the IoC Container?
Dependency injection means a class states what it needs and receives it, instead of creating it.
A controller that logs declares an ILogger<T> parameter in its constructor. It never calls a constructor on a logger, never reads configuration, and never knows which provider is behind it.
The container is what makes that work. Service registrations tell it how to build each type, and when it constructs the controller it supplies the arguments from those registrations.
Lifetime is the choice we make at registration. A singleton is built once for the application, a scoped service once per request, and a transient one every time it is asked for.
Loggers are registered for us, so nothing here has to be wired by hand. The registrations we wrote in part 2 and this part follow the same rules, and they are the reason a class can ask for an interface and receive a working implementation.
The three lifetimes are the choice we make every time we register something of our own:
AddSingletoncreates the service the first time it is requested, and every later request receives that same instance. Every component shares one object for the lifetime of the application.AddScopedcreates the service once per request. Each HTTP request gets its own instance, and everything handling that request shares it.AddTransientcreates the service every time it is asked for. If several components need it while handling one request, each of them gets a separate instance.
Conclusion
We added NLog to the Web API as a logging provider, configured a file target in nlog.config, and wrote messages from a controller through ILogger<T>. We also built the ILoggerManager wrapper the rest of this series injects, with the argument for it stated rather than assumed.
One thing is still out of reach at this point: messages produced before the host is built, which our article on logging from Program.cs before the host is built covers.
Thank you for reading, and next up is Repository Pattern in .NET Core Web API, where we start reading and writing the database. The series introduction page lists every part if you would rather jump around.
Logging in a production API goes further than a file on disk: correlation across requests, structured output a query can search, and where exceptions get caught. The Ultimate ASP.NET Core Web API course builds that on top of this same project.
Tested with .NET 10 and NLog.Web.AspNetCore 6.2.0.

For NLog 5.1.3 and Asp.Net Core 6 I was forced to add these lines to the “Program.cs” file, but I’m not sure if it was necessary:
var builder = WebApplication.CreateBuilder(args);
LogManager.Setup().LoadConfigurationFromAppSettings();
builder.Logging.ClearProviders();
builder.Host.UseNLog();
builder.Services.AddSingleton<ILogService, LogService>();
Hi Geo. I am not sure why you had to do that. In our book (both versions 6 and 7) we are using this approach and everything is working fine.
Hi,
The concept about DI is very easy to understand. Thanks for helping us understand all the topics in a crystal and clear manner
You are most welcome.
In nLog.Config from where i get Internal log file and File Name. internalLogFile=”d:Projects\Blog-AccountOwner\Project\internal_logs\internallog.txt”>
Or Simply I copy-paste the code of your nlog.config in my nlog.config file.
If you have D partition, you can copy-paste it, if not, just modify to C.
I have a Project in E drive and the Path of My project is E:\VS Projects\Net Core Projects\AccountOwnerServer what is the code of the Internal log file and File Name.
Just modify the path from the example from D to E and you should be good to go.
Hi,
Thank you very much for the tutorial. It is really well done.
I have a question regarding populating the nlog.config file. In the directions you tell us to provide our own paths for internalLogFile and fileName(picture attached). I am unsure what my path should be.
My solution is located here(inside AccountOwnerServer): /Users/craigreedgileswilliams/Desktop/workspace/aspDotNetCoreWebAPI/AccountOwnerServer
What should my path for these files be?
Thank you for you help!
-Craig
Hello Craig.
Try following paths:
internalLogFile=”.\internal_logs\internallog.txt”>
fileName=”.\logs\${shortdate}_logfile.txt”
With these, you can’t miss.
Thanks, Marinko!
The type ‘LoggerService.LoggerManager’ cannot be used as type parameter ‘TImplementation’ in the generic type or method ‘ServiceCollectionServiceExtensions.AddScoped<TService, TImplementation>(IServiceCollection)’. There is no implicit reference conversion from ‘LoggerService.LoggerManager’ to ‘Library_ASP.NET.Extensions.ILoggerManager’.
What can be the cause of this problem?
Well, LoggerService.LoggerManager is a custom implementation from the NLog namespace, and obviously, you can’t register it with the ILoggerManager that comes from the ASP.NET.Extensions namespace.
Hello,
Can you please make a tutorial on unit testing .Net Core web api using NUnit ?
Hello. We don’t have the article using NUnit but maybe this one with XUnit can help you https://code-maze.com/unit-testing-aspnetcore-web-api/. We will cover the NUnit as well soon.
I dont find
ServiceExtensionsclasshttps://github.com/CodeMazeBlog/.NET-Core-Series/tree/master/Part%203/AccountOwnerServer/Extensions
Hi,
Kindly show us logging using Serilog as well. It would be great to understand how to use Serilog for logging details in file as well in a database table.
Thanks
Hi Rohan. These are great topics and we will cover them for sure.
Thanks Marinko.
I don’t see any log files in specified path. I’ve already added log file path and file name similar to your nlog.config file. When running it, it calls successfully controller action and log writing code lines. but, I don’t see any log file in specified path. Thanks in advance.
As much as I would like to help, I don’t know where to start since I have no code or anything to work with. The best solution would be to download our source code, and compare it line by line with yours. Probably you have missed something, that happens a lot. You can start our project and verify that it is logging messages. If it does, then you missed something for sure.
Thank for reply. Can I get your source code link to download? Thank in advance
It is okay now. I remove {{urvanov-syntax-highlighter-internal:0}} in nlog.config file.
I am glad you solved the problem. By the way, the link for the source code is always at the beginning of the article.
Never a fan of local text files. Always a problem with directory resolution as I ran into right off trying to find the nlog configuration file. When I did write my own log files they went to a table in a database and all I had to do was insert a row.
This really depends on the project. If you have just a simple logging than the table would be sufficient. But if you have a large logging activity or if you have 10 microservices where each of them logs own activity, then having the logs in the database could be overload with additional tables and a lots and lots of logging rows. Of course, NLog provides support for the database logging as well.
Hi,
I have applied ASP.NET CORE 3.1 in my project.
I cannot add services.ConfigureLoggerService() to my startup.cs file
Hello Jahan. You are doing something wrong with the Logger IOC registration. I don’t have your code and can’t tell what is the problem. You can always compare your code with ours and find the differences.
Hello Marinko,
I have just ignored it and it works fine!
Thank you very much for your article. 🙂
Hi, good article. Will you make its continuation? I’ve followed all the steps, but I don’t know how to read NLog files or how to make it elsewhere on the Web Api website. Thanks
Hello. Thank you for reading this article. Well this is the ASP.NET Core Web API series of articles and we have continuation, just not related to logging actions. Of course there is a lot more to write about NLog, there is no doubt with that. You can read log files, if you need that, by using Directory and File classes from C#, there is nothing special about that (if I understood your question correctly). And I am not sure what you mean by “make it elsewhere on the Web Api website”. Right now your logging service is registered with IOC and you can use it whereever you like, just inject it through DI.
Best regards.
Thank you, your tutorials are really helpful.
Question: In the XML, I noticed that you use absolute path, won’t this approach be a problem during production?
Hello mate. I saw you comment on our GitHub repository and replied you there, didn’t you see it?
Haha silly me, No I did not see it. Let me quickly check it. Thanks for the quick response.
Hi Marinko. First of all, a big thank you for your tremendous effort for a detailed tutorial of this level. I bow in front of you.
I saw where we used DI in the ValuesController, however, I couldnt quite get where was the IOC that .NET Core was using to serve the service? Is it the ConfigureServices() method in the Startup.cs, that adds services to the container (more specifically, the line services.ConfigureLoggerService();?) Or is it another place? (I actually had no clue at first, but while I was writing this question, I just went back to the text, then to the code, and now it all makes sense.. we put a Singleton class in that method into the services IOC.) Thanks ^_^
Thank you very much. Hope you will enjoy the rest of the tutorial as well. Best regards.
First of all thanks for writing such a great tutorial , though I have few questions
Q1. For Asp.net core , NLog.Web.AspNetCore suggested ? Why we are using Nlog.Extension.Logging ? Actually I don’t know the difference. Please help me to understand ?
Q2. In .Net Core webapi project , I amusing Entity Framework(for core) to scaffold classes, Shall I need to refere to the same classes or shall I create different models as well.
Please Help. Thanks & Regards, A New .Net Core Developer. 🙂
Hello Deepak. We are using NLog because it is easy to use and it allows you better control of logging information. Of course you can use default logging solution integrated inside .NET Core, there is no problem with that at all, but I like to have better control of what content I em logging.
For you second question, if you are using Scaffolding, than stick to what has been created for you.
All the best.
The first question was regarding choice between
1) NLog.Web.AspNetCore
2) Nlog.Extension.Logging
Any difference between them , for .net core when u install package they prefer (1) ( as written in NLog website) but you implemented package num(2) , so I was asking any specific reason ?..
Regards & Thanks.
Ok I get it now, but I haven’t been using the first choice at all. This is from the official NLog site:
https://uploads.disquscdn.com/images/ff39e2e2d403c56cdb20e8a96aa1e8cd9f9fa1d670803414cc545537e9b5ba20.png
and as you can see they are stating that this library has a .net core support, so I didn’t search to much around, just saw that and used it 😀
Thanks for this step by step tutorial series. God bless you
You are very welcome. Glad to hear you like it and find it worth reading.
Hey thanks for the article. I’m just diving in to back-end development and I’m finding this very easy to understand. However, there’s a small issue that I need help with.
public Startup(IConfiguration configuration)
{
LogManager.LoadConfiguration(String.Concat(Directory.GetCurrentDirectory(), "/nlog.config"));
Configuration = configuration;
}
LogManager resides in NLog package if I’m not mistaken hence cannot be accessed from the Startup class I think since we added the NLog dependency to LoggerService and not AccountOwnerServer or am I missing something? Any sort of help is appreciated.
Also could you elaborate as to why we need a Contracts project in between when we could directly use the interface in LoggerService?
Hello Shahid, thank you for your comment. You are right that LogManager resides in NLog package and we have installed it inside the LoggerService project, but we have referenced that project to our main project thus we have a reference towards the NLog library. You can check that by expanding Dependencies > Projects > LoggerService.
We need the Contracts project due to the interface reusability mainly. Now if you want to implement another logger with some other library, but you want to log the same level messages, all you have to do is to create another project and just reference the Contracts project. Then you would have access to the ILoggerManager interface.
I hope this helps. All the best.
I was getting the same error and to fix this issue I have removed the reference of LoggerService from our main project and readded it and it works.
Thanks for your nice article! If I want to have a custom file name, how can I do it?
For example, when an error occurred, I produce a number for user then developer can figure out where the error occurred.
I want to change my file name error to this number.
Hello Jahan. Thank you for reading this article, I am glad you like it. About your question. Developer will always know where the error started due to the stack trace message. You can modify the name of your error log file, but all the modifications exist in the nlog.config file. I am not sure how to do it in the code. You can check this link https://github.com/nlog/NLog/wiki/File-target for more information about target files and how to archive them and name them.
Hi,
I want use DI in EF class library project, Is there any example available for class library?
I have many projects so i want to create separate class library and want to use DI in those class library. There are many examples but not how to use DI and many thing in separate EF core class library project like transaction management to execute opeartion in single block without raising deadlock etc. Is there any example available?
I came for the error handling stuff but have found so much more. I just want to say thanks heaps for this series, especially for making the amendments such as the nlog code update.
Thank you very much for your kind words. Don’t forget to subscribe, to get updated about our new posts. One more time thank you a lot.
Hello Martin. Thank you very much for reading the post and for your comment as well. I am so glad if you could find it useful. Regarding your questions:
1) As much as I know, NLog is not built in inside .NET Core Web API project. It is a third party, open source library. Please check this link: http://nlog-project.org/. NET Core has its own logging logic but I always like to use my way of controlling and logging messages. If you know something different or something has changed in the relation between .NET Core and NLog please share it with me.
2) Your implementation is very acceptable but there are two reasons for me to do it like I did. Firstly, the Startup constructor is going to fire up only once (when you start the server) and that is it, every other request won’t trigger this constructor therefore making NLog dependency on a single place as you mentioned and configured just once. The second reason and Main reason is that I had to extend the ILoggerFactory, which is built in in .NET Core, because without this extension NLog didn’t work when application was deployed on Linux environment (at least I had that problem).
I hope you are satisfied with my answers, and one more time, thank you very much for reading and making your suggestions. It is always a pleasure to talk with .NET skillful people 😀
One more time thank you so much mate. It is always a pleasure to talk with people like you. Hope I will see you around. All the best mate.
Best regards,
Marinko.
Thanks for the post. For project reference, you have “Also, add a reference from the Contracts project to the LoggerService project.” This should be the other way around. Right? We should be referencing LoggerService project from Contract project to make use of the interface. When I added a reference from Contracts project to LoggerService project, I got some circular reference error. other then that, everything works fine. I was able to test the logging service in the end.
Hi Sumu (again 😀 ) Well you just misunderstood me or I wrote it in the wrong way. But to explain my self: With the sentence “Also, add a reference from the Contracts project to the LoggerService project.” I meant in the LoggerService right click on the Dependencies and click AddReference, then choose Contracts and import it’s reference inside LoggerService project. Other way around you are going to get circular reference, as you mentioned. I am sorry for misleading sentence, I am going to make it more clear for understanding. Thank you very much for the suggestion. And thank you for reading our posts.
Ok. Thanks!
Hello Marinko, i was reading the comments as i was facing the same problem. Do you mean import LoggerService as reference in Contracts project, or import Contracts as reference in LoggerService project?
Based on your tutorial above, it seems like you are saying we have to do both. But i encounter the circular error when i try to do it that way.
Please advise.
Hi James. Could you please point where is stated to do both? I have to fix that. You need to import Contract reference to the Logger project. The main interface that Logger is using exists in the Contracts project .