// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Threading; using System.Threading.Tasks; using System.Web.Http.Properties; namespace System.Web.Http.ExceptionHandling { /// Represents an unhandled exception logger. public abstract class ExceptionLogger : IExceptionLogger { internal const string LoggedByKey = "MS_LoggedBy"; /// Task IExceptionLogger.LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken) { if (context == null) { throw new ArgumentNullException("context"); } ExceptionContext exceptionContext = context.ExceptionContext; Contract.Assert(exceptionContext != null); if (!ShouldLog(context)) { return TaskHelpers.Completed(); } return LogAsync(context, cancellationToken); } /// When overridden in a derived class, logs the exception asynchronously. /// The exception logger context. /// The token to monitor for cancellation requests. /// A task representing the asynchronous exception logging operation. public virtual Task LogAsync(ExceptionLoggerContext context, CancellationToken cancellationToken) { Log(context); return TaskHelpers.Completed(); } /// When overridden in a derived class, logs the exception synchronously. /// The exception logger context. public virtual void Log(ExceptionLoggerContext context) { } /// Determines whether the exception should be logged. /// The exception logger context. /// /// if the exception should be logged; otherwise, . /// /// /// The default decision is only to log an exception instance the first time it is seen by this logger. /// public virtual bool ShouldLog(ExceptionLoggerContext context) { if (context == null) { throw new ArgumentNullException("context"); } ExceptionContext exceptionContext = context.ExceptionContext; Contract.Assert(exceptionContext != null); IDictionary data = exceptionContext.Exception.Data; if (data == null || data.IsReadOnly) { // If the exception doesn't have a mutable Data collection, we can't prevent duplicate logging. In this // case, just log every time. return true; } ICollection loggedBy; if (data.Contains(LoggedByKey)) { object untypedLoggedBy = data[LoggedByKey]; loggedBy = untypedLoggedBy as ICollection; if (loggedBy == null) { // If exception.Data["MS_LoggedBy"] exists but is not of the right type, we can't prevent duplicate // logging. In this case, just log every time. return true; } if (loggedBy.Contains(this)) { // If this logger has already logged this exception, don't log again. return false; } } else { loggedBy = new List(); data.Add(LoggedByKey, loggedBy); } // Either loggedBy did not exist before (we just added it) or it already existed of the right type and did // not already contain this logger. Log now, but mark not to log this exception again for this logger. Contract.Assert(loggedBy != null); loggedBy.Add(this); return true; } } }