I have a library assembly that outputs trace information, and a client winforms application that adds a trace listener via app.config. If I were to use the library in ASP.NET, not configured for System.Diagnostics tracing, how could I 'catch' the trace output?
Bonus question: Can I do something with Elmah to catch and log this info? Our ASP.NET app currently uses Elmah for error logging, but that's all I know on that side of things.
I think that, as long as the library outputs trace information, this information could be captured with any specialized tool (like DebugView) or even a house grown tool, regardless of ASP.NET configuration.
I see this is old and answered, but.. I have just had the same issue and I came up with
Exception class for tracelistener
namespace TrueNorth.Elmah.TraceListener
{
internal class TraceInformation : Exception
{
internal TraceInformation(string message) : base(message){}
}
internal class TraceError: Exception
{
internal TraceError(string message) : base(message) { }
}
internal class TraceWarning : Exception
{
internal TraceWarning(string message) : base(message) { }
}
internal class TraceWrite : Exception
{
internal TraceWrite(string message) : base(message) { }
}
}
The listener
namespace TrueNorth.Elmah.TraceListener
{
internal class ElmahListener : System.Diagnostics.TraceListener
{
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
{
TraceEvent(eventCache, source, eventType, id, string.Format(format, args));
}
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) //
{
Exception exception;
switch (eventType)
{
case TraceEventType.Information:
exception = new TraceInformation(message);
break;
case TraceEventType.Error:
exception = new TraceError(message);
break;
case TraceEventType.Warning:
exception = new TraceWarning(message);
break;
default:
exception = new TraceWrite(message);
break;
}
if (HttpContext.Current.Session == null)
{
ErrorLog.GetDefault(null).Log(new Error(exception));
}
else
{
ErrorSignal.FromCurrentContext().Raise(exception );
}
}
public override void TraceTransfer(TraceEventCache eventCache, string source, int id, string message, Guid relatedActivityId)
{
base.TraceTransfer(eventCache, source, id, message, relatedActivityId);
}
public override void Write(string message)
{
}
public override void WriteLine(string message)
{
}
}
}
And, the GO code ( you can use your web.config)
Tracer.Register();
blogged at http://truenorthit.co.uk/2015/04/17/trace-listener-for-elmah-asp-mvc-exception-logger/
write code for attaching a trace file listener programtically and enable tracing in web.config - you will be done after that
Related
I am trying to write a Spring WS client using WebServiceGatewaySupport. I managed to test the client for a successful request and response. Now I wanted to write test cases for soap faults.
public class MyClient extends WebServiceGatewaySupport {
public ServiceResponse method(ServiceRequest serviceRequest) {
return (ServiceResponse) getWebServiceTemplate().marshalSendAndReceive(serviceRequest);
}
#ActiveProfiles("test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = SpringTestConfig.class)
#DirtiesContext
public class MyClientTest {
#Autowired
private MyClient myClient;
private MockWebServiceServer mockServer;
#Before
public void createServer() throws Exception {
mockServer = MockWebServiceServer.createServer(myClient);
}
}
My question is how do i stub the soap fault response in the mock server, so that my custom FaultMessageResolver will be able to unmarshall soap fault?
I tried couple of things below, but nothing worked.
// responsePayload being SoapFault wrapped in SoapEnvelope
mockServer.expect(payload(requestPayload))
.andRespond(withSoapEnvelope(responsePayload));
// tried to build error message
mockServer.expect(payload(requestPayload))
.andRespond(withError("soap fault string"));
// tried with Exception
mockServer.expect(payload(requestPayload))
.andRespond(withException(new RuntimeException));
Any help is appreciated. Thanks!
Follow Up:
Ok so, withSoapEnvelope(payload) I managed to get the controller to go to my custom MySoapFaultMessageResolver.
public class MyCustomSoapFaultMessageResolver implements FaultMessageResolver {
private Jaxb2Marshaller jaxb2Marshaller;
#Override
public void resolveFault(WebServiceMessage message) throws IOException {
if (message instanceof SoapMessage) {
SoapMessage soapMessage = (SoapMessage) message;
SoapFaultDetailElement soapFaultDetailElement = (SoapFaultDetailElement) soapMessage.getSoapBody()
.getFault()
.getFaultDetail()
.getDetailEntries()
.next();
Source source = soapFaultDetailElement.getSource();
jaxb2Marshaller = new Jaxb2Marshaller();
jaxb2Marshaller.setContextPath("com.company.project.schema");
Object object = jaxb2Marshaller.unmarshal(source);
if (object instanceof CustomerAlreadyExistsFault) {
throw new CustomerAlreadyExistsException(soapMessage);
}
}
}
}
But seriously!!! I had to unmarshall every message and check the instance of it. Being a client I should be thorough with all possible exceptions of the service here, and create custom runtime exceptions and throw it from the resolver. Still at the end, its been caught in WebServiceTemplate and re thrown as just a runtime exception.
You could try with something like this:
#Test
public void yourTestMethod() // with no throw here
{
Source requestPayload = new StringSource("<your request>");
String errorMessage = "Your error message from WS";
mockWebServiceServer
.expect(payload(requestPayload))
.andRespond(withError(errorMessage));
YourRequestClass request = new YourRequestClass();
// TODO: set request properties...
try {
yourClient.callMethod(request);
}
catch (Exception e) {
assertThat(e.getMessage()).isEqualTo(errorMessage);
}
mockWebServiceServer.verify();
}
In this part of code mockWebServiceServer represents the instance of MockWebServiceServer class.
We have rest api application. We use redis for API response caching and internal method caching. If redis connection then it is making our API down. We want to bypass the redis caching if that redis connection fails or any exception instead of making our API down.
There is a interface CacheErrorHandler but it handles the redis get set operation failures not redis connection problems. We are using Spring 4.1.2.
Let's boil this down a bit. Your application uses caching (implemented with Redis). If the Redis connection is stale/closed or otherwise, then you want the application to bypass caching and (presumably) go directly to an underlying data store (e.g. RDBMS). The application Service logic might look similar to...
#Service
class CustomerService ... {
#Autowired
private CustomerRepository customerRepo;
protected CustomerRepository getCustomerRepo() {
Assert.notNull(customerRepo, "The CustomerRepository was not initialized!");
return customerRepo;
}
#Cacheable(value = "Customers")
public Customer getCustomer(Long customerId) {
return getCustomerRepo().load(customerId);
}
...
}
All that matters in Spring core's Caching Abstraction to ascertain a Cache "miss" is that the value returned is null. As such, Spring Caching Infrastructure will then proceed in calling the actual Service method (i.e. getCustomer). Keep in mind on the return of the getCustomerRepo().load(customerId) call, you also need to handle the case where Spring's Caching Infrastructure attempts to now cache the value.
In the spirit of keeping it simple, we will do without AOP, but you should be able to achieve this using AOP as well (your choice).
All you (should) need is a "custom" RedisCacheManager extending the SDR CacheManager implementation, something like...
package example;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
...
class MyCustomRedisCacheManager extends RedisCacheManager {
public MyCustomerRedisCacheManager(RedisTemplate redisTemplate) {
super(redisTemplate);
}
#Override
public Cache getCache(String name) {
return new RedisCacheWrapper(super.getCache(name));
}
protected static class RedisCacheWrapper implements Cache {
private final Cache delegate;
public RedisCacheWrapper(Cache redisCache) {
Assert.notNull(redisCache, "'delegate' must not be null");
this.delegate = redisCache;
}
#Override
public Cache.ValueWrapper get(Object key) {
try {
delegate.get(key);
}
catch (Exception e) {
return handleErrors(e);
}
}
#Override
public void put(Object key, Object value) {
try {
delegate.put(key, value);
}
catch (Exception e) {
handleErrors(e);
}
}
// implement clear(), evict(key), get(key, type), getName(), getNativeCache(), putIfAbsent(key, value) accordingly (delegating to the delegate).
protected <T> T handleErrors(Exception e) throws Exception {
if (e instanceof <some RedisConnection Exception type>) {
// log the connection problem
return null;
}
else if (<something different>) { // act appropriately }
...
else {
throw e;
}
}
}
}
So, if Redis is unavailable, perhaps the best you can do is log the problem and proceed to let the Service invocation happen. Clearly, this will hamper performance but at least it will raise awareness that a problem exists. Clearly, this could be tied into a more robust notification system, but it is a crude example of the possibilities. The important thing is, your Service remains available while the other services (e.g. Redis) that the application service depends on, may have failed.
In this implementation (vs. my previous explanation) I chose to delegate to the underlying, actual RedisCache implementation to let the Exception occur, then knowing full well a problem with Redis exists, and so that you can deal with the Exception appropriately. However, if you are a certain that the Exception is related to a connection problem upon inspection, you can return "null" to let Spring Caching Infrastructure proceed as if it were a Cache "miss" (i.e. bad Redis Connection == Cache miss, in this case).
I know something like this should help your problem as I built a similar prototype of a "custom" CacheManager implementation for GemFire and one of Pivotal's customers. In that particular UC, the Cache "miss" had to be triggered by an "out-of-date version" of the application domain object where production had a mix of newer and older application clients connecting to GemFire through Spring's Caching Abstraction. The application domain object fields would change in newer versions of the app for instance.
Anyway, hope this helps or gives you more ideas.
Cheers!
So, I was digging through the core Spring Framework Caching Abstraction source today addressing another question and it seems if a CacheErrorHandler is implemented properly, then perhaps a problematic Redis Connection could still result in the desired behavior, e.g. cache "miss" (triggered with the return of a null value).
See the AbstractCacheInvoker source for more details.
The cache.get(key) should result in an exception due to a faulty Redis Connection and thus Exception handler would be invoked...
catch (RuntimeException e) {
getErrorHandler().handleCacheGetError(e, cache, key);
return null; // If the exception is handled, return a cache miss
}
If the CacheErrorHandler properly handles the Cache "get" error (and does not re-throw the/an Exception), then a null value will be returned indicating a cache "miss".
Thank you #John Blum. My solution in Spring Boot is as follows.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.Cache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;
import java.util.concurrent.Callable;
class CustomRedisCacheManager extends RedisCacheManager {
private static Logger logger = LoggerFactory.getLogger(CustomRedisCacheManager.class);
public CustomRedisCacheManager(RedisOperations redisOperations) {
super(redisOperations);
}
#Override
public Cache getCache(String name) {
return new RedisCacheWrapper(super.getCache(name));
}
protected static class RedisCacheWrapper implements Cache {
private final Cache delegate;
public RedisCacheWrapper(Cache redisCache) {
Assert.notNull(redisCache, "delegate cache must not be null");
this.delegate = redisCache;
}
#Override
public String getName() {
try {
return delegate.getName();
} catch (Exception e) {
return handleException(e);
}
}
#Override
public Object getNativeCache() {
try {
return delegate.getNativeCache();
} catch (Exception e) {
return handleException(e);
}
}
#Override
public Cache.ValueWrapper get(Object key) {
try {
return delegate.get(key);
} catch (Exception e) {
return handleException(e);
}
}
#Override
public <T> T get(Object o, Class<T> aClass) {
try {
return delegate.get(o, aClass);
} catch (Exception e) {
return handleException(e);
}
}
#Override
public <T> T get(Object o, Callable<T> callable) {
try {
return delegate.get(o, callable);
} catch (Exception e) {
return handleException(e);
}
}
#Override
public void put(Object key, Object value) {
try {
delegate.put(key, value);
} catch (Exception e) {
handleException(e);
}
}
#Override
public ValueWrapper putIfAbsent(Object o, Object o1) {
try {
return delegate.putIfAbsent(o, o1);
} catch (Exception e) {
return handleException(e);
}
}
#Override
public void evict(Object o) {
try {
delegate.evict(o);
} catch (Exception e) {
handleException(e);
}
}
#Override
public void clear() {
try {
delegate.clear();
} catch (Exception e) {
handleException(e);
}
}
private <T> T handleException(Exception e) {
logger.error("handleException", e);
return null;
}
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisTemplate;
#Configuration
public class RedisConfig {
#Bean
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate) {
CustomRedisCacheManager redisCacheManager = new CustomRedisCacheManager(redisTemplate);
redisCacheManager.setUsePrefix(true);
return redisCacheManager;
}
}
actually my response is directed to Mr. #Vivek Aditya - I faced the same problem: new spring-data-redis api and not constructing RedisCacheManager per RedisTemplate. The only option - based on #John Blum suggestions - was to use aspects. And below is my code.
#Aspect
#Component
public class FailoverRedisCacheAspect {
private static class FailoverRedisCache extends RedisCache {
protected FailoverRedisCache(RedisCache redisCache) {
super(redisCache.getName(), redisCache.getNativeCache(), redisCache.getCacheConfiguration());
}
#Override
public <T> T get(Object key, Callable<T> valueLoader) {
try {
return super.get(key, valueLoader);
} catch (RuntimeException ex) {
return valueFromLoader(key, valueLoader);
}
}
private <T> T valueFromLoader(Object key, Callable<T> valueLoader) {
try {
return valueLoader.call();
} catch (Exception e) {
throw new ValueRetrievalException(key, valueLoader, e);
}
}
}
#Around("execution(* org.springframework.cache.support.AbstractCacheManager.getCache (..))")
public Cache beforeSampleCreation(ProceedingJoinPoint proceedingJoinPoint) {
try {
Cache cache = (Cache) proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
if (cache instanceof RedisCache) {
return new FailoverRedisCache((RedisCache) cache);
} else {
return cache;
}
} catch (Throwable ex) {
return null;
}
}
}
works fine for all reasonable scenarios:
app starts fine with redis down
app (still) works during (sudden) redis outage
when redis starts working again, app sees it
Edit: the code is more like a poc - only for "get", and I don't like reinstantiating FailoverRedisCache every single cache hit - there should be a map.
None of the above worked for us when using Spring Boot 2.3.9.release with Redis. We ended up creating and registering our own customized CacheErrorHandler named CustomCacheErrorHandler to override the default SimpleCacheErrorHandler provided by Spring Framework. This will work perfectly.
#Configuration
public class CachingConfiguration extends CachingConfigurerSupport {
#Override
public CacheErrorHandler errorHandler() {
return new CustomCacheErrorHandler();
}
}
class CustomCacheErrorHandler implements CacheErrorHandler {
Logger log = Logger.get(CustomCacheErrorHandler.class);
#Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object o) {
log.error(e.getMessage(), e);
}
#Override
public void handleCachePutError(RuntimeException e, Cache cache, Object o, Object o1) {
log.error(e.getMessage(), e);
}
#Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object o) {
log.error(e.getMessage(), e);
}
#Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error(e.getMessage(), e);
}
}
I had same problem, but, unfortunately, none of the above solutions work for me. I checked for the problem and found out that the executed command never timed out if there was no connection to Redis. So I start to study lettuce library for a solution. I solve the problem by rejecting the command when there is no connection:
#Bean
public LettuceConnectionFactory lettuceConnectionFactory()
{
final SocketOptions socketOptions = SocketOptions.builder().connectTimeout(Duration.ofSeconds(10)).build();
ClientOptions clientOptions = ClientOptions.builder()
.socketOptions(socketOptions)
.autoReconnect(true)
.disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS)
.build();
LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
.commandTimeout(Duration.ofSeconds(10))
.clientOptions(clientOptions).build();
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(this.host, this.port);
return new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
}
All the core Spring Framework Cache abstraction annotations (e.g. #Cacheable) along with the JSR-107 JCache annotations supported by the core SF delegate to the underlying CacheManager under-the-hood, and for Redis, that is the RedisCacheManager.
You would configure the RedisCacheManager in Spring XML configuration meta-data similar to here.
One approach would be to write an AOP Proxy for the (Redis)CacheManager that uses the RedisConnection (indirectly from the RedisTemplate) to ascertain the state of the connection on each (Redis)CacheManger operation.
If the connection has failed, or is closed, for standard cache ops, the (Redis)CacheManager could return an instance of RedisCache for getCache(String name) that always returns null (indicating a Cache miss on an entry), thus passing through to the underlying data store.
There maybe better ways to handle this as I am not an expert on all things Redis (or SDR), but this should work and perhaps give you a few ides of your own.
Cheers.
You can use CacheErrorHandler. But you should make sure to make
RedisCacheManager transactionAware to false in your Redis Cache Config(to make sure the transaction is committed early when executing the caching part and the error is caught by CacheErrorHandler and don't wait until the end of the execution which skips CacheErrorHandler part). The function to set transactionAware to false looks like this:
#Bean
public RedisCacheManager redisCacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
JdkSerializationRedisSerializer redisSerializer = new JdkSerializationRedisSerializer(getClass().getClassLoader());
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(redisDataTTL))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer));
redisCacheConfiguration.usePrefix();
RedisCacheManager redisCacheManager = RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
.cacheDefaults(redisCacheConfiguration)
.build();
redisCacheManager.setTransactionAware(false);
return redisCacheManager;
}
we use a StateServer for handling Session for the known benefits (web farm, IIS recycling).
However I am trying to figure out how to make this fault tolerant. Nothing we store in the Session is critical, it is just used for performance. So if the StateServer is not available we are happy to reload from disk.
However there appears to be no way of detecting if the StateServer is online or not, so the following code all runs fine even if the StateServer is down
try
{
//It is not NULL as it has been configured
if (HttpContext.Current.Session != null)
Session["Test"] = "value";
}
// No exception is thrown
catch (Exception)
{
throw new Exception();
}
Now it makes sense to me that no exception is thrown. The Session handling would not be very performant if it had to check the status on every write. So I am guessing what happens is that it writes all the Session vaiables when the Response is written.
There lies the problem, when it tries to write the Session it fails with a 500 error and I do not know anyway to intercept this error and handle it.
Unable to make the session state request to the session state server.
Please ensure that the ASP.NET State service is started and that the
client and server ports are the same.
What I would like to happen is that the write just fails silently (or logs an error) and clients are not impacted. As it is write now the entire site goes down due to this single point of failure.
Any ideas - am I missing something obvious?
well, it can be hard. Asp.net uses session tightly, so if session storage fails, it asp.net will also fails during initialization of session module. You can write own session state provider, that will wrap existing one, and in case of fail it will return empty session items, but it can be hard to use it, because session behavior can be unpredictable.
You can look into built in SQL session state provider, that has failover in case if your SQL server has replication.
UPDATE1
Here is example of wrapper for default session providers
public class SessionProviderWrapper : SessionStateStoreProviderBase
{
private readonly SessionStateStoreProviderBase _provider;
private static Func<SessionStateStoreProviderBase> _createProvider;
static SessionProvider()
{
_createProvider = InitializerProvider();
}
private static Func<SessionStateStoreProviderBase> InitializerProvider()
{
if (_createProvider != null)
return _createProvider;
var sessionType = "stateserver"; // you can switch to another session provider
Type type;
switch (sessionType)
{
case "inproc":
type = Type.GetType("System.Web.SessionState.InProcSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
break;
case "sql":
type = Type.GetType("System.Web.SessionState.SqlSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
break;
case "stateserver":
type = Type.GetType("System.Web.SessionState.OutOfProcSessionStateStore, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
break;
default:
throw new ConfigurationErrorsException("Unknow session type: " + sessionType);
}
if (type == null)
{
throw new InvalidOperationException("Failed to find session provider for " + sessionType);
}
_createProvider = GenerateConstructorCall(type);
return _createProvider;
}
private static Func<SessionStateStoreProviderBase> GenerateConstructorCall(Type type)
{
// we are searching for public constructor
var constructor = type.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 0);
if (constructor == null)
{
// otherwise for internal. SQL session provider has internal constructor, but we don't care
constructor = type.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance).FirstOrDefault(c => c.GetParameters().Length == 0);
}
var node = Expression.New(constructor);
var lambda = Expression.Lambda<Func<SessionStateStoreProviderBase>>(node, null);
var func = lambda.Compile();
return func;
}
public SessionProvider()
{
var createProvider = InitializerProvider();
_provider = createProvider();
}
public override void Initialize(string name, NameValueCollection config)
{
_provider.Initialize(name, config);
}
public override string Name
{
get { return _provider.Name; }
}
public override string Description
{
get { return _provider.Description; }
}
public override void Dispose()
{
_provider.Dispose();
}
public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
{
return _provider.SetItemExpireCallback(expireCallback);
}
public override void InitializeRequest(HttpContext context)
{
_provider.InitializeRequest(context);
}
public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
out SessionStateActions actions)
{
try
{
return _provider.GetItem(context, id, out locked, out lockAge, out lockId, out actions);
}
catch (Exception ex)
{
locked = false;
lockAge = TimeSpan.Zero;
lockId = null;
actions = SessionStateActions.None;
// log ex
return new SessionStateStoreData(new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 10);
}
}
public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId,
out SessionStateActions actions)
{
return _provider.GetItemExclusive(context, id, out locked, out lockAge, out lockId, out actions);
}
public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
{
_provider.ReleaseItemExclusive(context, id, lockId);
}
public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
{
_provider.SetAndReleaseItemExclusive(context, id, item, lockId, newItem);
}
public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
{
_provider.RemoveItem(context, id, lockId, item);
}
public override void ResetItemTimeout(HttpContext context, string id)
{
_provider.ResetItemTimeout(context, id);
}
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
{
return _provider.CreateNewStoreData(context, timeout);
}
public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
{
_provider.CreateUninitializedItem(context, id, timeout);
}
public override void EndRequest(HttpContext context)
{
_provider.EndRequest(context);
}
}
Basically you can make try\catch on each method like in GetItem method, and in case of error, you can return empty session object. If it fails in try\catch application still will be alive. But performance will be decreased as for each request it will throw a couple of exceptions on Get\Release, that will be handled in catch section. But anyway these exceptions will decrease performance a bit
I would like to accept tgolisch answer as a solution that works for me.
In Global.asax we will look for the missing StateServer error in the Application_Error event
If we find it we will use Server.ClearError() and log the error
We will also use this to log the error and possibly send out an alert
Thanks all!
I'm starting a web application that contains the following projects:
Booking.Web
Booking.Services
Booking.DataObjects
Booking.Data
I'm using the repository pattern in my data project only. All services will be the same, no matter what happens. However, if a customer wants to use Access, it will use a different data repository than if the customer wants to use SQL Server.
I have StructureMap, and want to be able to do the following:
Web project is unaffected. It's a web forms application that will only know about the services project and the dataobjects project.
When a service is called, it will use StructureMap (by looking up the bootstrapper.cs file) to see which data repository to use.
An example of a services class is the error logging class:
public class ErrorLog : IErrorLog
{
ILogging logger;
public ErrorLog()
{
}
public ErrorLog(ILogging logger)
{
this.logger = logger;
}
public void AddToLog(string errorMessage)
{
try
{
AddToDatabaseLog(errorMessage);
}
catch (Exception ex)
{
AddToFileLog(ex.Message);
}
finally
{
AddToFileLog(errorMessage);
}
}
private void AddToDatabaseLog(string errorMessage)
{
ErrorObject error =
new ErrorObject
{
ErrorDateTime = DateTime.Now,
ErrorMessage = errorMessage
};
logger.Insert(error);
}
private void AddToFileLog(string errorMessage)
{
// TODO: Take this value from the web.config instead of hard coding it
TextWriter writer = new StreamWriter(#"E:\Work\Booking\Booking\Booking.Web\Logs\ErrorLog.txt", true);
writer.WriteLine(DateTime.Now.ToString() + " ---------- " + errorMessage);
writer.Close();
}
}
I want to be able to call this service from my web project, without defining which repository to use for the data access. My boostrapper.cs file in the services project is defined as:
public class Bootstrapper
{
public static void ConfigureStructureMap()
{
ObjectFactory.Initialize(x =>
{
x.AddRegistry(new ServiceRegistry());
}
);
}
public class ServiceRegistry : Registry
{
protected override void configure()
{
ForRequestedType<IErrorLog>().TheDefaultIsConcreteType<Booking.Services.Logging.ErrorLog>();
ForRequestedType<ILogging>().TheDefaultIsConcreteType<SqlServerLoggingProvider>();
}
}
}
What else do I need to get this to work? When I defined a test, the ILogger object was null.
Perhaps some details on how you are calling this code from a test would be useful.
My understanding is that you need to ensure that the ConfigureStructureMap call has been made early in the applications life (e.g. in the Global.asax in a web project).
After that you would be calling for instances of IErrorLog using something like:
IErrorLog log = StructureMap.ObjectFactory.GetNamedInstance<IErrorLog>();
I'm using the [System.Web.Script.Services.ScriptService] tag to use web services callable from client side javascript. What I need is a way of globally logging any unhandled exceptions in those methods. On the client side, I get the error callback and can proceed from there, but I need a server-side catch to log the exception.
The guy at this url:
http://ayende.com/Blog/archive/2008/01/06/ASP.Net-Ajax-Error-Handling-and-WTF.aspx
suggests that this can't be done.
Is that accurate? Do I seriously have to go to every single webmethod in the entire system and try/catch the method as a whole.
You can use an HTTP module to capture the exception message, stack trace and exception type that is thrown by the web service method.
First some background...
If a web service method throws an exception the HTTP response has a status code of 500.
If custom errors are off then the web
service will return the exception
message and stack trace to the client
as JSON. For example:{"Message":"Exception
message","StackTrace":" at
WebApplication.HelloService.HelloWorld()
in C:\Projects\Stackoverflow
Examples\WebApplication\WebApplication\HelloService.asmx.cs:line
22","ExceptionType":"System.ApplicationException"}
When custom errors are on then the
web service returns a default message
to the client and removes the stack
trace and exception type:{"Message":"There was an error processing the request.","StackTrace":"","ExceptionType":""}
So what we need to do is set custom errors off for the web service and plug in an HTTP module that:
Checks if the request is for a web service method
Checks if an exception was thrown - that is, a status code of 500 is being returned
If 1) and 2) are true then get the original JSON that would be sent to the client and replace it with the default JSON
The code below is an example of an HTTP module that does this:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web;
public class ErrorHandlerModule : IHttpModule {
public void Init(HttpApplication context) {
context.PostRequestHandlerExecute += OnPostRequestHandlerExecute;
context.EndRequest += OnEndRequest;
}
static void OnPostRequestHandlerExecute(object sender, EventArgs e) {
HttpApplication context = (HttpApplication) sender;
// TODO: Update with the correct check for your application
if (context.Request.Path.StartsWith("/HelloService.asmx")
&& context.Response.StatusCode == 500) {
context.Response.Filter =
new ErrorHandlerFilter(context.Response.Filter);
context.EndRequest += OnEndRequest;
}
}
static void OnEndRequest(object sender, EventArgs e) {
HttpApplication context = (HttpApplication) sender;
ErrorHandlerFilter errorHandlerFilter =
context.Response.Filter as ErrorHandlerFilter;
if (errorHandlerFilter == null) {
return;
}
string originalContent =
Encoding.UTF8.GetString(
errorHandlerFilter.OriginalBytesWritten.ToArray());
// If customErrors are Off then originalContent will contain JSON with
// the original exception message, stack trace and exception type.
// TODO: log the exception
}
public void Dispose() { }
}
This module uses the following filter to override the content sent to the client and to store the original bytes (which contain the exception message, stack trace and exception type):
public class ErrorHandlerFilter : Stream {
private readonly Stream _responseFilter;
public List OriginalBytesWritten { get; private set; }
private const string Content =
"{\"Message\":\"There was an error processing the request.\"" +
",\"StackTrace\":\"\",\"ExceptionType\":\"\"}";
public ErrorHandlerFilter(Stream responseFilter) {
_responseFilter = responseFilter;
OriginalBytesWritten = new List();
}
public override void Flush() {
byte[] bytes = Encoding.UTF8.GetBytes(Content);
_responseFilter.Write(bytes, 0, bytes.Length);
_responseFilter.Flush();
}
public override long Seek(long offset, SeekOrigin origin) {
return _responseFilter.Seek(offset, origin);
}
public override void SetLength(long value) {
_responseFilter.SetLength(value);
}
public override int Read(byte[] buffer, int offset, int count) {
return _responseFilter.Read(buffer, offset, count);
}
public override void Write(byte[] buffer, int offset, int count) {
for (int i = offset; i < offset + count; i++) {
OriginalBytesWritten.Add(buffer[i]);
}
}
public override bool CanRead {
get { return _responseFilter.CanRead; }
}
public override bool CanSeek {
get { return _responseFilter.CanSeek; }
}
public override bool CanWrite {
get { return _responseFilter.CanWrite; }
}
public override long Length {
get { return _responseFilter.Length; }
}
public override long Position {
get { return _responseFilter.Position; }
set { _responseFilter.Position = value; }
}
}
This method requires custom errors to be switched off for the web services. You would probably want to keep custom errors on for the rest of the application so the web services should be placed in a sub directory. Custom errors can be switched off in that directory only using a web.config that overrides the parent setting.
You run the Stored Procedure in the backend. Then, for a single variable, it returns more than 1 value. Because of that, a conflicts occurs, and, this error is thrown.
I know this doesn't answer the question per-say, but I went on my own quest a while back to find this out and would up empty handed. Ended up wrapping each web service call in a try/catch, and the catch calls our error logger. Sucks, but it works.
In ASP.Net it is possible to catch all run handled exceptions using a global error handler although the blog post suggest this would not work but you could experiment with this approach trying to rethrow the error in some way?
Another idea would be to look at the open source elmah (Error Logging Modules and Handlers) for ASP.Net that might help or someone in that community may have an idea.