We will create sample_service_hosting .Net core console project, which will implement our SampleService that will post messages to Google Logging Interface.
1. Create sample_service_hosting .Net Core console project
>mkdir sample_service_hosting >cd sample_service_hosting >dotnet new consoleAdd the following package references
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<ItemGroup> | |
<PackageReference Include="Google.Cloud.Logging.NLog" Version="2.1.0" /> | |
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.2.0" /> | |
<PackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="2.2.0" /> | |
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" /> | |
<PackageReference Include="Microsoft.Extensions.Hosting" Version="2.2.0" /> | |
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" /> | |
<PackageReference Include="Microsoft.Extensions.Logging.Configuration" Version="2.2.0" /> | |
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" /> | |
<PackageReference Include="nlog" Version="4.6.6" /> | |
<PackageReference Include="NLog.Extensions.Hosting" Version="1.5.2" /> | |
</ItemGroup> |
NLog, NLog.Extensions.Hosting and Google.Cloud.Logging.NLog are also added for a final stage of the project to support logging messages to Google Cloud Log Viewer.
2. Implement HostBuilder configuration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.IO; | |
using System.Threading.Tasks; | |
using Microsoft.Extensions.Configuration; | |
using Microsoft.Extensions.DependencyInjection; | |
using Microsoft.Extensions.Hosting; | |
using Microsoft.Extensions.Logging; | |
using NLog.Extensions.Hosting; | |
namespace VSC | |
{ | |
class Program | |
{ | |
static async Task Main(string[] args) | |
{ | |
IHostBuilder hostBuilder = CreateHostBuilder(args); | |
if(hostBuilder != null) | |
{ | |
IHost host = hostBuilder.Build(); | |
await host.RunAsync(); | |
} | |
} | |
private static IHostBuilder CreateHostBuilder(string[] args) | |
{ | |
try | |
{ | |
var builder = new HostBuilder() | |
.ConfigureAppConfiguration((hostingContext, config) => | |
{ | |
config.SetBasePath(Directory.GetCurrentDirectory()); | |
config.AddJsonFile("appsettings.json", optional: true); | |
config.AddJsonFile( | |
$"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", | |
optional: true); | |
config.AddCommandLine(args); | |
}) | |
.ConfigureServices((hostContext, services) => | |
{ | |
services.AddHostedService<Services.SampleService>(); | |
services.Configure<HostOptions>(option => | |
{ | |
option.ShutdownTimeout = System.TimeSpan.FromSeconds(20); | |
}); | |
}) | |
.ConfigureLogging((hostingContext, logging) => | |
{ | |
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); | |
logging.AddConsole(); | |
}); | |
return builder; | |
} | |
catch { } | |
return null; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"Logging": { | |
"LogLevel": { | |
"Default": "Trace", | |
"Microsoft": "Information" | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.Extensions.Hosting; | |
namespace VSC.Services | |
{ | |
public class SampleService : IHostedService, IDisposable | |
{ | |
public void Dispose() | |
{ | |
// TODO: Do service clean up here | |
} | |
public Task StartAsync(CancellationToken cancellationToken) | |
{ | |
// TODO: Do work here | |
return Task.CompletedTask; | |
} | |
public Task StopAsync(CancellationToken cancellationToken) | |
{ | |
// stop service processes here | |
return Task.CompletedTask; | |
} | |
} | |
} |
3. Implement SampleService hosted service
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.Extensions.Hosting; | |
using Microsoft.Extensions.Logging; | |
namespace VSC.Services | |
{ | |
public class SampleService : IHostedService, IDisposable | |
{ | |
private CancellationTokenSource _cancellationTokenSource; | |
private Task _executingTask; | |
ILogger<SampleService> _logger; | |
public SampleService( | |
ILogger<SampleService> logger | |
) | |
{ | |
_logger = logger; | |
} | |
public void Dispose() | |
{ | |
// TODO: Do service clean up here | |
} | |
public Task StartAsync(CancellationToken cancellationToken) | |
{ | |
_logger.LogTrace("SampleService StartAsync method called."); | |
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); | |
_executingTask = Run(_cancellationTokenSource.Token); | |
return Task.CompletedTask; | |
} | |
public async Task StopAsync(CancellationToken cancellationToken) | |
{ | |
_logger.LogTrace("SampleService StopAsync method called."); | |
// Stop called without start | |
if (_executingTask == null) | |
{ | |
return; | |
} | |
// stop service processes here | |
_cancellationTokenSource.Cancel(); | |
_logger.LogInformation("Sample Service stopped."); | |
// Wait until the task completes or the stop token triggers | |
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken)); | |
} | |
private async Task Run(CancellationToken cancellationToken) | |
{ | |
_logger.LogTrace("Starting iteration count Run"); | |
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); | |
int iterationCount = 0; | |
while (!cancellationToken.IsCancellationRequested) | |
{ | |
iterationCount++; | |
_logger.LogInformation(string.Format("Running round {0}", iterationCount)); | |
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); | |
} | |
} | |
} | |
} |
Logger is logging Trace messages and information messages within iteration loop. Add nlog.config to project.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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" | |
internalLogLevel="info"> | |
<!-- enable asp.net core layout renderers --> | |
<extensions> | |
<add assembly="NLog.Web.AspNetCore"/> | |
</extensions> | |
<!-- the targets to write to --> | |
<targets> | |
<!-- write logs to file --> | |
<target name="asyncLogFile" xsi:type="AsyncWrapper"> | |
<target | |
xsi:type="File" | |
name="logfile" | |
fileName="log/log-${shortdate}.txt" | |
archiveFileName="log/archive/log-${shortdate}.txt" | |
keepFileOpen="true" | |
archiveAboveSize="20000000" | |
concurrentWrites="false" | |
archiveEvery="Day" | |
layout="${longdate}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" /> | |
</target> | |
<target name="asyncConsole" xsi:type="AsyncWrapper"> | |
<target name="console" xsi:type="ColoredConsole" | |
layout="${date} ${threadname:whenEmpty=${threadid}} ${level:uppercase=true} ${logger} ${message} ${onexception:${exception}}" /> | |
</target> | |
</targets> | |
<!-- rules to map from logger name to target --> | |
<rules> | |
<!--All logs--> | |
<logger name="*" minlevel="Trace" writeTo="asyncLogFile" /> | |
<logger name="*" minlevel="Info" writeTo="asyncConsole" /> | |
<logger name="Microsoft.*" maxLevel="Info" final="true" /> <!-- BlackHole without writeTo --> | |
</rules> | |
</nlog> |
4. Enabling gradual stop of the service on SIGTERM signal
We can deploy this sample_service_hosting app to Linux VM and make it run as a daemon, but with its current implementation it will not properly respond to SIGTERM sent from Linux host to gradually stop and clean up used resources. For this we are going to use injected IApplicationLifetime object and register events ApplicationStarted, ApplicationStopping and ApplicationStoped.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using Microsoft.Extensions.Hosting; | |
using Microsoft.Extensions.Logging; | |
namespace VSC.Services | |
{ | |
public class SampleService : IHostedService, IDisposable | |
{ | |
private CancellationTokenSource _cancellationTokenSource; | |
private Task _executingTask; | |
IApplicationLifetime _appLifetime; | |
ILogger<SampleService> _logger; | |
public SampleService( | |
ILogger<SampleService> logger, | |
IApplicationLifetime appLifetime | |
) | |
{ | |
_logger = logger; | |
_appLifetime = appLifetime; | |
} | |
public void Dispose() | |
{ | |
// TODO: Do service clean up here | |
} | |
public Task StartAsync(CancellationToken cancellationToken) | |
{ | |
_logger.LogTrace("SampleService StartAsync method called."); | |
_appLifetime.ApplicationStarted.Register(OnStarted); | |
_appLifetime.ApplicationStopping.Register(OnStopping); | |
_appLifetime.ApplicationStopped.Register(OnStopped); | |
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); | |
_executingTask = Run(_cancellationTokenSource.Token); | |
return Task.CompletedTask; | |
} | |
public async Task StopAsync(CancellationToken cancellationToken) | |
{ | |
_logger.LogTrace("SampleService StopAsync method called."); | |
// Stop called without start | |
if (_executingTask == null) | |
{ | |
return; | |
} | |
// stop service processes here | |
_cancellationTokenSource.Cancel(); | |
_logger.LogInformation("Sample Service stopped."); | |
// Wait until the task completes or the stop token triggers | |
await Task.WhenAny(_executingTask, Task.Delay(-1, cancellationToken)); | |
} | |
private void OnStarted() | |
{ | |
_logger.LogTrace("SampleService OnStarted method called."); | |
// Post-startup code goes here | |
} | |
private void OnStopping() | |
{ | |
_logger.LogTrace("SampleService OnStopping method called."); | |
// On-stopping code goes here | |
} | |
private void OnStopped() | |
{ | |
_logger.LogTrace("SampleService OnStopped method called."); | |
// Post-stopped code goes here | |
} | |
private async Task Run(CancellationToken cancellationToken) | |
{ | |
_logger.LogTrace("Starting iteration count Run"); | |
await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); | |
int iterationCount = 0; | |
while (!cancellationToken.IsCancellationRequested) | |
{ | |
iterationCount++; | |
_logger.LogInformation(string.Format("Running round {0}", iterationCount)); | |
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken); | |
} | |
} | |
} | |
} |
5. NLog.config - Add support for Stackdriver logging
At the start we already added reference to Google.Cloud.Logging.NLog package. Now we need to update nlog.config file to include assembly Google.Cloud.Logging.NLog and GoogleStackdriver target to log to Google Logging Interface.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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" | |
internalLogLevel="info"> | |
<!-- enable asp.net core layout renderers --> | |
<extensions> | |
<add assembly="NLog.Web.AspNetCore"/> | |
<add assembly="Google.Cloud.Logging.NLog"/> | |
</extensions> | |
<!-- the targets to write to --> | |
<targets> | |
<!-- write logs to file --> | |
<target name="asyncLogFile" xsi:type="AsyncWrapper"> | |
<target | |
xsi:type="File" | |
name="logfile" | |
fileName="log/log-${shortdate}.txt" | |
archiveFileName="log/archive/log-${shortdate}.txt" | |
keepFileOpen="true" | |
archiveAboveSize="20000000" | |
concurrentWrites="false" | |
archiveEvery="Day" | |
layout="${longdate}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" /> | |
</target> | |
<target name="asyncConsole" xsi:type="AsyncWrapper"> | |
<target name="console" xsi:type="ColoredConsole" | |
layout="${date} ${threadname:whenEmpty=${threadid}} ${level:uppercase=true} ${logger} ${message} ${onexception:${exception}}" /> | |
</target> | |
<target name="asyncStackDriver" xsi:type="AsyncWrapper"> | |
<target name="stackDriver" | |
xsi:type="GoogleStackdriver" | |
logId="Default" | |
layout="${longdate}|${uppercase:${level}}|${logger}|${message} ${exception:format=tostring}" /> | |
</target> | |
</targets> | |
<!-- rules to map from logger name to target --> | |
<rules> | |
<!--All logs--> | |
<logger name="*" minlevel="Trace" writeTo="asyncLogFile" /> | |
<logger name="*" minlevel="Info" writeTo="asyncConsole" /> | |
<logger name="*" minlevel="Trace" writeTo="asyncStackDriver" /> | |
<logger name="Microsoft.*" maxLevel="Info" final="true" /> <!-- BlackHole without writeTo --> | |
</rules> | |
</nlog> |
Conclusion
Our sample_service_hosting app is done. Check the final version of this project on github. I am not going to cover here setup of this app as a daemon on Linux VM Google Cloud Compute Engine. This will be a topic of separate blog post. Stay tuned.
References:
- Clean service stop by handling SIGTERM on Linux with .NET Core 2.1
- Integrating NLog with Stackdriver for error reporting on Google Cloud