I am new to servicestack and elman logging.
Can any body suggest how do we integrate elmah in service stack applications.
Thank you...
If you have an existing logging solution then you can use the ServiceStack.Logging.Elmah project. It is available via NuGet.
Exceptions, errors and fatal calls will be logged to Elmah in addition to the originally intended logger. For all other log types, only the original logger is used.
So if you are already using Log4Net then you can just configure Elmah like this
ElmahLogFactory factory = new ElmahLogFactory(new Log4NetFactory());
If you don't want to wrap in over an existing log then you can just research adding Elmah to any ASP.NET website. There is no reason it wouldn't work just because you are using ServiceStack.
using ServiceStack.Logging;
using ServiceStack.Logging.Elmah;
using ServiceStack.Logging.NLogger;
public AppHost()
: base(
"description",
typeof(MyService).Assembly)
{
LogManager.LogFactory = new ElmahLogFactory(new NLogFactory());
}
public override void Configure(Container container)
{
this.ServiceExceptionHandler += (request, exception) =>
{
// log your exceptions here
HttpContext context = HttpContext.Current;
ErrorLog.GetDefault(context).Log(new Error(exception, context));
// call default exception handler or prepare your own custom response
return DtoUtils.HandleException(this, request, exception);
};
// rest of your config
}
}
Now your ServiceStack error's appear in Elmah (assuming you've setup web.config etc).
Actually kampsj answer is better than Gavin's as Gavins causes double-logging to elmah by calling explicit elmah logger and then the default servicestack error handling...which itself already does the logging.
So really all you need is this (below assuming you want to wrap NLog with Elmah)
public class YourAppHost : AppHostBase
{
public YourAppHost() //Tell ServiceStack the name and where to find your web services
: base("YourAppName", typeof(YourService).Assembly)
{
LogManager.LogFactory = new ElmahLogFactory(new NLogFactory());
}
//...just normal stuff...
}
You could just have this above:
ElmahLogFactory factory = new ElmahLogFactory();
...but you probably should wrap another type of logger for non-error logging, like Debug and Warn.
This section on configuring Elmah and the Logging.Elmah UseCase for a working example of ServiceStack and Elmah configured together.
The ElmahLogFactory can be configured in your Global.asax before initializing the ServiceStack AppHost, e.g:
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
var debugMessagesLog = new ConsoleLogFactory();
LogManager.LogFactory = new ElmahLogFactory(debugMessagesLog, this);
new AppHost().Init();
}
}
Related
I've got an Azure function which I'm adding Application Insights logging to.
It uses autofac to manage the dependencies in a config file, and I have added as follows:
private static void RegisterDependencies(ContainerBuilder containerBuilder)
{
containerBuilder.RegisterType<ApplicationInsightsLoggerFactory>()
.As<ILoggerFactory>()
.SingleInstance();
containerBuilder.Register(client => new TelemetryClient(new TelemetryConfiguration("[instrumentation key]"))).As<TelemetryClient>().SingleInstance();
...
In Program.cs 'Main', this class is called like this IocConfig.Configure()
The function runs with no errors, but the logging is not appearing in Application Insights. Have I missed something in this configuration?
You don't to write the DI yourself to get a TelemetryClient. The built-in DI is already available for you to just get one:
public class MyFunctionClass
{
private readonly TelemetryClient telemetryClient;
/// Using dependency injection will guarantee that you use the same configuration for telemetry collected automatically and manually.
public MyFunctionClass(TelemetryConfiguration telemetryConfiguration)
{
this.telemetryClient = new TelemetryClient(telemetryConfiguration);
}
I'm trying to get some additional information about the message we received (basically its application-level outcome) into the Application Insights RequestTelemetry object for a WCF service.
Application Insights is logging request telemetry already. I created an ITelemetryInitializer that is being run, but at the time it runs I have no way that I can find to access information about the request, much less application-specific data from the request's context.
Is there somewhere I can put data that will be accessible by the ITelemetryInitializer at the time it runs?
public class WcfServiceTelemetryInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
if (telemetry is RequestTelemetry rTelemetry)
{
// HttpContext.Current is populated at this point, but doesn't seem to be available within my application code.
// So is System.ServiceModel.OperationContext.Current
}
}
}
I had to face similar issue as the author described. Tried by implementing ITelemetryInitializer/ITelemetryProcessor but did not work.
Ended up writing my own MessageTraceTelemetryModule class implementing IWcfTelemetryModule and IWcfMessageTrace.
In the OnTraceResponse method, I added my custom property to the request telemetry by extracting value from OperationContext (which is accessible here!):
internal class MessageTraceTelemetryModule : IWcfTelemetryModule, IWcfMessageTrace
{
public void OnTraceResponse(IOperationContext operation, ref Message response)
{
if (OperationContext.Current.IncomingMessageProperties.TryGetValue("clientID", out object value))
{
operation.Request.Properties.Add("clientID", value.ToString());
}
}
}
New custom property visible in Application Insights telemetry - ClientID custom property Pic.
Note that the clientID property is being set in the OperationContext in Message Inspector:
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
if(!OperationContext.Current.IncomingMessageProperties.ContainsKey("clientID"))
OperationContext.Current.IncomingMessageProperties.Add("clientID", clientID);
}
Brief Context:
I implemented AAD Token based Authentication in a SOAP based WCF Service.
I needed to store the clientID from the token (which is validated in message inspector) and add the same as a custom property in the application insights request telemetry.
References:
Message Inspectors Documentation
Application Insights for WCF Documentation
In case it helps anyone, Application Insights automatically adds custom dimensions from data you store in System.Diagnostics.Activity.Current.AddBaggage(), or at least it does in asp.net 5. That might be available at the right place for you in WCF land.
e.g.
var currentActivity = System.Diagnostics.Activity.Current;
if (currentActivity != null)
{
currentActivity.AddBaggage("MyPropertyName", someData);
}
To log custom property, you can try like this...
public class CustomTelemetry : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var requestTelemetry = telemetry as RequestTelemetry;
if (requestTelemetry == null) return;
requestTelemetry.Properties.Add("CustomProperty", "DummyValue");
}
}
And register CustomTelemetry at start of the application
TelemetryConfiguration.Active.TelemetryInitializers.Add(new CustomTelemetry());
Here is the Original Answer
MS Doc - Application Insights API for custom events and metrics
Is there a non-obvious way (to me at least) to add a custom sink e.g. MongoDB or MicrosoftTeams as part of instantiating the Serilog factory in the ServiceStack framework or will it be a case of rolling your own factory and implementation of ILog?
PM> Install-Package ServiceStack.Logging.Serilog
LogManager.LogFactory = new SerilogFactory();
ServiceStack Logging
Serilog
Example: MongoDB Sink
This works without using the ServiceStack implementation, but is it considered bad form?
public override void Configure(Container container)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.MongoDBCapped("mongodb://mymongourl:27017/mylogs",
collectionName: "mycollectionoflogs", cappedMaxSizeMb: 50,
cappedMaxDocuments: 10000)
.CreateLogger();
SetConfig(new HostConfig
{
DefaultRedirectPath = "/metadata",
DebugMode = AppSettings.Get(nameof(HostConfig.DebugMode), false)
});
}
and in the ServiceInterface message implementation:
public object Any(MyRequest request)
{
Log.Information("I'm a lumberjack and I'm OK");
return new MyRequestResponse
{
Result = $"{ results.Chop() }"
};
}
I've just added a constructor overload to use a custom Serilog logger in this commit, this change is available from v5.1.1 that's now available on MyGet.
With this change you can pass a custom Serilog logger with ServiceStack's SerilogFactory, e.g:
LogManager.LogFactory = new SerilogFactory(new LoggerConfiguration()
.WriteTo.MongoDBCapped("mongodb://mymongourl:27017/mylogs",
collectionName: "mycollectionoflogs", cappedMaxSizeMb: 50,
cappedMaxDocuments: 10000)
.CreateLogger());
You can use the Serilog logger directly like in your example except it wont be able to capture ServiceStack's built-in logs or be able to substitute it later with any of the other ServiceStack loggers.
My Web Api when run locally (in Release mode) will return any errors in this format:
{
"Message": "An error has occurred.",
"ExceptionMessage": "No text specified",
"ExceptionType": "System.Exception",
"StackTrace": null
}
But after deployment/publish to an Azure VM, only this remains:
{
"Message": "An error has occurred."
}
API code:
try
{
var msg = ...
new MessageService().SaveMessage(msg)); // <-- does some checks; may throw.
return Ok();
}
catch (Exception ex)
{
return InternalServerError(ex);
}
I'd like it to be more detailed on Azure, like the local result.
Can this be achieved, and if so, how?
I already (temporarily) removed <compilation xdt:Transform="RemoveAttributes(debug)" /> from the <system.web> part of Web.Release.config, and then re-deployed, but that made no difference.
Or am I using the wrong approach/pattern?
Obviously technical details should be limited, but right now we get no details at all.
You could try adding the following to your Global.asax:
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
Note: I wouldn't recommend that you keep this setting on in a production environment.
If instead you use
GlobalConfiguration.Configuration.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Default;
then you can use the system.webServer error switch e.g.
<system.webServer>
<httpErrors errorMode="Detailed" existingResponse="PassThrough">
</httpErrors>
</system.webServer>
Note the existingResponse attribute to preserve the error message.
I had the same problem, the post is three years old, things have changed a little. If you setup a new Azure Mobile App with Visual Studio 2017 there is no longer a Global.asax.cs. I searched for hours, where to put this IncludeErrorDetailPolicy. It won't work without that setting.
You do it in your Startup.MobileApp.cs:
public partial class Startup
{
public static void ConfigureMobileApp(IAppBuilder app)
{
HttpConfiguration config = new HttpConfiguration();
config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always;
new MobileAppConfiguration()
.UseDefaultConfiguration()
.ApplyTo(config);
Don't forget, in your Web.config you also need to set:
<system.webServer>
<httpErrors errorMode="Detailed" existingResponse="PassThrough">
</httpErrors>
</system.webServer>
Don't use that for production environment!
For Web API 2, you can implement a custom IExceptionLogger that utilizes Azure Application Insights. Something like this:
using Microsoft.ApplicationInsights;
using System.Web.Http.ExceptionHandling;
namespace Your.Namespace.Here
{
public class TelemetryExceptionLogger : ExceptionLogger
{
private readonly TelemetryClient telemetryClient;
public TelemetryExceptionLogger(TelemetryClient telemetryClient)
{
this.telemetryClient = telemetryClient;
}
public override void Log(ExceptionLoggerContext context)
{
if (context != null && context.Exception != null)
{
telemetryClient.TrackException(context.Exception);
}
base.Log(context);
}
}
}
Then you need to register it with Web API:
using Microsoft.ApplicationInsights;
using System.Web.Http;
using System.Web.Http.ExceptionHandling;
using Your.Namespace.Here;
namespace Some.Other.Namespace.Or.The.Same
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// --- Normal Web API configuration here ---
config.Services.Add(typeof(IExceptionLogger), new TelemetryExceptionLogger(new TelemetryClient()));
}
}
}
For this to work, you will need to have set up Application Insight in Azure and for your VS project, but that is a story for another time :)
For more information, see Application Insights: Exception Telemetry
I have a scenario with the same error, and the problem was a copy&paste in the route header attribute of a method. I have the same route for two methods
[Route("test/Method1")]
public IHttpActionResult Method1(){...}
[Route("test/Method1")]
public IHttpActionResult Method2(){...}
Check the new methods and Routes added.
Is it possible/easy to mock NLog log methods, using Rhino Mocks or similar?
Using Nuget : install-package NLog.Interface
Then: ILogger logger = new LoggerAdapter([logger-from-NLog]);
You can only mock virtual methods. But if You create some interface for logging and then implement it using NLog You can use dependency injection and in Your tests use mocked interface to see if system under test (SUT) is logging what You expect it to log.
public class SUT
{
private readonly ILogger logger;
SUT(ILogger logger) { this.logger = logger;}
MethodUnderTest() {
// ...
logger.LogSomething();
// ...
}
}
// and in tests
var mockLogger = new MockLogger();
var sut = new SUT(mockLogger);
sut.MethodUnderTest();
Assert.That("Expected log message", Is.Equal.To(mockLogger.LastLoggedMessage));
The simple answer, is 'no'. Looking at the code, dependency-injection is not supported, which seems rather an oversight, especially as it doesn't look difficult to implement (at first glance).
The only interfaces in the project are there to support COM interop objects and a few other things. The main Logger concrete class neither implements an interface, nor provides virtual methods.
You could either provide an interface yourself, or use Moles/TypeMock/ another isolation framework to mock the dependency.
I've used code like this to stub out the NLog logging code. You can make use of NLog's MemoryTarget which just keeps messages in memory until it's disposed of. You can query the content of the log using Linq or whatever (this example uses FluentAssertions)
using FluentAssertions
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NLog;
using NLog.Config;
using NLog.Targets;
...
private MemoryTarget _stubLogger;
[TestInitialize]
public void Setup()
{
ConfigureTestLogging();
}
protected virtual LoggingConfiguration GetLoggingConfiguration()
{
var config = new NLog.Config.LoggingConfiguration();
this._stubLogger = new MemoryTarget();
_stubLogger.Layout = "${level}|${message}";
config.AddRule(LogLevel.Debug, LogLevel.Fatal, this._stubLogger);
return config;
}
protected virtual void ConfigureTestLogging()
{
var config = GetLoggingConfiguration();
NLog.LogManager.Configuration = config;
}
[TestMethod]
public void ApiCallErrors_ShouldNotThrow()
{
// arrange
var target = new Thing();
// act
target.DoThing();
// assert
this._stubLogger.Logs.Should().Contain(l =>
l.Contains("Error|") &&
l.Contains("Expected Message"));
}