I have a dotnet core console application build to connect to a Sql Service Broker instance to monitor table changes.
The app monitors one table that is updated from an ERP system and then publishes messages to our bus.
It runs fine when running as a console application, or debugging in my IDE.
I am having an issue when using TopShelf to configure it as a windows service.
Here is the entry point:
private static void Main(string[] args)
{
RegisterComponents();
var serviceHost = HostFactory.Run(sc =>
{
sc.Service<ISalesOrderMonitorService>(s =>
{
var sqlListener = _container.ResolveNamed<SqlDependencyEx>(ListenerKey.SalesOrder);
var changeHandler = _container.Resolve<ISalesOrderChangeHandler>();
var listenerConfig = _container.ResolveNamed<ListenerConfiguration>(ListenerKey.SalesOrder);
var logger = _container.Resolve<ILogger<SalesOrder>>();
s.ConstructUsing(f =>
new SalesOrderMonitorService(sqlListener, changeHandler, listenerConfig, logger));
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
});
var exitCode = (int) Convert.ChangeType(serviceHost, serviceHost.GetType());
Environment.ExitCode = exitCode;
}
The "worker" class:
public abstract class ServiceBase<T, TZ> : IService<T>
where T : IChangeHandler
{
protected readonly IChangeHandler ChangeHandler;
protected readonly SqlDependencyEx Listener;
protected readonly ListenerConfiguration ListenerConfiguration;
protected readonly ILogger<TZ> Logger;
protected ServiceBase(SqlDependencyEx listener, IChangeHandler changeHandler,
ListenerConfiguration listenerConfiguration, ILogger<TZ> logger)
{
Logger = logger;
ListenerConfiguration = listenerConfiguration;
Listener = listener;
ChangeHandler = changeHandler;
}
public virtual void Start()
{
try
{
Listener.TableChanged += (o, e) => ChangeHandler.Process(e);
Listener.Start();
Logger.LogDebug(
$"Listening to changes on the {ListenerConfiguration.Table} table in the {ListenerConfiguration.Database} database");
}
catch (Exception e)
{
Logger.LogError(e, e.Message);
throw;
}
}
public virtual void Stop()
{
Listener.Stop();
}
Install through TopShelf is no problem:
c:>{ServiceName}.exe install -username "serviceAccount" -password "superSecret" -servicename "ServiceName" -servicedescription "Description" -displayname "Service DisplayName" --autostart
When I go to start the service - I get this:
This is misleading because the event viewer shows this:
This is happening way faster than 30 seconds. This is definitely related to how I am configuring TopShelf.
As stated - the application works just fine when run "debug" or even as just an exe console.
I got it figured out. Actually both comments from #DotNetPadawan and #Lex Li indirectly got me there.
For starters - enabling the remote debugger clued me in that my appsetting.json was not being read into my IConfiguration. That was really confusing because everything works fine running locally with a debugger or even just starting the exe.
The link Lex Li points out did not provide the answer - however that article had this reference:
Host and Deploy aspnetcore as a Windows Service
It was here that I found this little nugget:
The current working directory returned by calling GetCurrentDirectory for a Windows Service is the C:\WINDOWS\system32 folder. The system32 folder isn't a suitable location to store a service's files (for example, settings files). Use one of the following approaches to maintain and access a service's assets and settings files.
The link explains how to conditionally set the current directory if the app is running as a service.
var isConsole = args.Contains("-mode:console");
if (!isConsole)
{
var pathToExe = Process.GetCurrentProcess().MainModule?.FileName;
var pathToContentRoot = Path.GetDirectoryName(pathToExe);
Directory.SetCurrentDirectory(pathToContentRoot);
}
Putting this out there for anyone else that runs into this problem.
Admittedly - netcore 3.0 is likely the better way to go - but I don't have the bandwidth to upgrade everything is this repo (lots of shared stuff) to 3.0. I needed to get this working.
Related
I have used Serilog in the past and enriched logs with various properties. However I am playing around with the new AWS Lambda Serverless templates using .net core 3.1 and cannot get the logs to enrich. I have tried this in a basic Console app as well with the same result.
Example: code:
class Program
{
static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.AWSSeriLog(GetAwsSerilogConfiguration())
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.CreateLogger();
using (LogContext.PushProperty("A", 1))
{
Log.Information("Starting web host");
Console.WriteLine("Hello World!");
}
}
private static AWSLoggerConfig GetAwsSerilogConfiguration()
{
var environmentName = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
return new AWSLoggerConfig
{
LogGroup = $"AwsLoggingTestConsoleApp/{environmentName ?? "Development"}",
Region = RegionEndpoint.APSoutheast2.SystemName
};
}
}
The following screenshots from Cloudwatch Logs and Insights show the log generated by this code. The enriched property 'A' and MachineName do not appear
Cloudwatch Logs
Cloudwatch Insights
I feel like I am missing something obvious, but for the life of me I cannot see it.
Any ideas why the logs are not being enriched?
Many thanks,
M.
I'm working on a .NET Core 2.0 web application. I got a mocked database where application works good. Today i created a clean database and got this error 500 both on IIS Express and on regular IIS.
The thing is: i couldn't debug why is throwing the error 500. The application just run.
I've currently tried:
Check IIS logs by activating stdoutLogEnabled="true" and the only log created was:
Hosting environment: Production Content root path:
C:\Users\pistolon\Downloads\peto Now listening on:
http://localhost:13361 Application started. Press Ctrl+C to shut down.
Debugging from start the startup.cs file.
When I switch back to the mocked db it works without error.
Do you any of you could point me on where could i get an exception or log for this?
You can use Event Viewer or Visual Studio Remote Debugging to debug your deployed applications.
Also, you can use a logging framework like Serilog and use one of it sinks like file sink and create a middleware which catches and logs your exceptions and write their StackTrace, Message, Source to a log file that you can read them.
Here is ErrorHandlingMiddleware implementation :
public class ErrorHandlingMiddleware
{
readonly RequestDelegate _next;
static ILogger<ErrorHandlingMiddleware> _logger;
public ErrorHandlingMiddleware(RequestDelegate next,
ILogger<ErrorHandlingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
static async Task HandleExceptionAsync(
HttpContext context,
Exception exception)
{
string error = "An internal server error has occured.";
_logger.LogError($"{exception.Source} - {exception.Message} - {exception.StackTrace} - {exception.TargetSite.Name}");
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new
{
error
}));
}
}
Usage :
app.UseMiddleware<ErrorHandlingMiddleware>();
I don't believe this is enough information to go off of, however if you're seeing that 500 error when using the real database and the mock database is working appropriately, I would bet that your issue is with the connection string. You can also check to ensure that you're in the development environment as well.
Update: You can also try to use app.UseDeveloperExceptionPage();
I have a Windows Services running as a Publisher and I am trying to setup Xamarin Forms as the Subscriber. The code below works just fine in a Console App or LinqPad, but when copied and pasted into Xamarin Forms, the SubscriberSocket just does not respond to messages from the server.
Do you know how to wire this up?
I am using NetMQ v 4.0.0.1
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
Task.Run(() => StartPubSubSocketSubscriber());
}
private void StartPubSubSocketSubscriber()
{
string topic = "TopicA";
using (var subSocket = new SubscriberSocket())
{
subSocket.Options.ReceiveHighWatermark = 1000;
subSocket.Connect("tcp://192.168.30.120:5556");
subSocket.Subscribe(topic);
while (true)
{
string messageTopicReceived = subSocket.ReceiveFrameString();
string messageReceived = subSocket.ReceiveFrameString();
Device.BeginInvokeOnMainThread(() =>
{
label.Text = messageReceived;
});
}
}
}
}
I also tried starting the background thread with Task.Factory.StartNew(() => StartPubSubSocketSubscriber(), TaskCreationOptions.LongRunning); but it is just as unresponsive to messages from the publisher.
Thank you.
PS.: removed subSocket.Connect("tcp://localhost:5556");
The fix for this was a 2 step process:
The SubscriberSocket was incorrectly pointing to localhost. An understandable mistake since the emulator runs on the same machine as the server application. Make sure the Subscriber has the explicit IP address when running on a virtual environment or another device.
The issue with SubscriberSocket not responding was actually on a server. I had set it up with pubSocket.Bind("tcp://localhost:5556");, once I changed it to pubSocket.Bind("tcp://*:5556"); the SubscriberSocket started responding. This is an error in documentation.
The hint to the solution came from the NetMQ github issue tracking:
https://github.com/zeromq/netmq/issues/747
I have created a custom telemetry processor which is adding customer properties to the telemetry items. When running locally, I don’t see any issue and I am seeing the properties being added(both in release and debug mode) and logged to the AppInsights.
When deployed to app service, I am seeing the logs, but the properties being added by the telemetry processor are missing.
I am using .Net Core 2.1 and the Application insights NuGet version is 2.3.0. Is there a way to debug why this is happening? Also is anyone aware if this is a known issue?
Can you share your code which adds application insights and the telemetry processor? It should be something like this:
public void ConfigureServices(IServiceCollection services)
{
// ...
services.AddApplicationInsightsTelemetry();
services.AddApplicationInsightsTelemetryProcessor<MyFirstCustomTelemetryProcessor>();
// If you have more processors:
services.AddApplicationInsightsTelemetryProcessor<MySecondCustomTelemetryProcessor>();
// ...
}
Can you try to print the list of TelemetryProcessors registered with the TelemetryConfiguration instance? You can constructor inject TelemetryConfiguration to a controller class, and print out the list. Something like shown below.
string tpList;
public ValuesController(TelemetryConfiguration tc)
{
var tps = tc.TelemetryProcessors;
foreach(var tp in tps)
{
var s = tp.GetType().ToString();
tpList += s;
}
}
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2", tpList };
}
This should confirm if the TelemetryProcessor is even in the config.
Also, when you say works local - do you mean when running from Visual Studio? VS alters behavior, so can try to run locally outside of VS, and see if it reproes.
How can I check inside the application if it is being hosted in IIS?
Check if the environment variable APP_POOL_ID is set.
public static bool InsideIIS() =>
System.Environment.GetEnvironmentVariable("APP_POOL_ID") is string;
All of environment variables that iis sets on a child process
I've tried the answer by Branimir Ričko but found that it's not correct: this environment variable is also set when running under IIS express.
So here is my modified version:
static bool IsRunningInsideIIS() =>
System.Environment.GetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES") is string startupAssemblies &&
startupAssemblies.Contains(typeof(Microsoft.AspNetCore.Server.IISIntegration.IISDefaults).Namespace);
I believe there is no direct way how to achieve that out of the box. At least I haven't found one. And the reason, as I can tell is the fact ASP.NET Core application is actually a self-contained application knowing nothing about it's parent context, unless the later will reveal information about itself.
For example in the configuration file we can tell which type of the installation we're running: production or development. We can assume that production is IIS, while development is not. However that didn't worked for me. Since my production setup could be either IIS or windows service.
So I have worked around this problem by supplying different command line arguments to my application depending on type of run it supposed to perform. That, actually, came naturally for me, since windows service indeed requires different approach to run.
For example in my case code looked somewhat like so:
namespace AspNetCore.Web.App
{
using McMaster.Extensions.CommandLineUtils;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.WindowsServices;
using System;
using System.Diagnostics;
using System.IO;
public class Program
{
#region Public Methods
public static IWebHostBuilder GetHostBuilder(string[] args, int port) =>
WebHost.CreateDefaultBuilder(args)
.UseKestrel()
.UseIISIntegration()
.UseUrls($"http://*:{port}")
.UseStartup<Startup>();
public static void Main(string[] args)
{
var app = new CommandLineApplication();
app.HelpOption();
var optionHosting = app.Option("--hosting <TYPE>", "Type of the hosting used. Valid options: `service` and `console`, `console` is the default one", CommandOptionType.SingleValue);
var optionPort = app.Option("--port <NUMBER>", "Post will be used, `5000` is the default one", CommandOptionType.SingleValue);
app.OnExecute(() =>
{
//
var hosting = optionHosting.HasValue()
? optionHosting.Value()
: "console";
var port = optionPort.HasValue()
? new Func<int>(() =>
{
if (int.TryParse(optionPort.Value(), out var number))
{
// Returning successfully parsed number
return number;
}
// Returning default port number in case of failure
return 5000;
})()
: 5000;
var builder = GetHostBuilder(args, port);
if (Debugger.IsAttached || hosting.ToLowerInvariant() != "service")
{
builder
.UseContentRoot(Directory.GetCurrentDirectory())
.Build()
.Run();
}
else
{
builder
.UseContentRoot(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName))
.Build()
.RunAsService();
}
});
app.Execute(args);
}
#endregion Public Methods
}
}
This code not only allows select type of the hosting (service and console — the option that IIS supposed to use), but also allows to change port which is important, when you're running as Windows service.
Another good thing is usage of argument parsing library, McMaster.Extensions.CommandLineUtils — it will show information about configured command line switches, so it would be easy to select right values.