How to 'Logging' without adding a line to every method - c#

I have a C# web api project, basically I need to log everytime a method is getting called (ie. method name, parameters passed, result, stacktrace (if error occured), etc).
Right now, I did this by adding few lines to every method and it seems bloated, is there more efficient way to achieve this?
Any help will be appreciated.
Thank you

Middleware is one approach, or you can also create a Filter that lets you perform 'before' and 'after' operations,
https://www.tutorialsteacher.com/webapi/web-api-filters
[Code from the above link]
public class LogDemoAttribute : Attribute, IActionFilter
{
public LogDemoAttribute()
{
}
public async Task<HttpResponseMessage> ExecuteActionFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
Trace.WriteLine(string.Format("Action Method {0} executing at {1}", actionContext.ActionDescriptor.ActionName, DateTime.Now.ToShortDateString()), "Web API Logs");
var result = await continuation();
Trace.WriteLine(string.Format("Action Method {0} executed at {1}", actionContext.ActionDescriptor.ActionName, DateTime.Now.ToShortDateString()), "Web API Logs");
return result;
}
public bool AllowMultiple
{
get { return true; }
}
}
This does let you add to the Global Filters (in which case, you might as well use a Middleware), or add it selectively to certain endpoints, e.g
[ApiController]
public class PeopleController : ControllerBase
{
[LogDemo]
[HttpGet("demo/endpoint")]
public IActionResult GetAll()
{
}
}
This has the advantage that you might want to pass in some parameters to the attribute, that let you perform certain context specific behaviours.
If you want to add logging to non-controller code, then you can take this approach further to an Aspect Orientated / Decorator pattern.
https://learn.microsoft.com/en-us/archive/msdn-magazine/2014/february/aspect-oriented-programming-aspect-oriented-programming-with-the-realproxy-class
I generally find logging parameters at the controller level is good enough, and something like Application Insights that generates telemetry that shows you what the request looks like is actually the useful bit of information.

When you're saying "method" I'm unsure if you mean literally every method or just whenever a controller is being hit. But middleware would probably be the way to go. The documentation on adding custom middleware is here: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?view=aspnetcore-3.1
And here is an example of error handling middleware, where you could place your logging: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-3.1

Related

C# ASP.NET Core Web API: How to maintain context in the controller between calling different methods

C# ASP.NET Core Web API: general newbie question. Trying to implement basic Asynchronous Request-Reply Pattern. Basic idea is that:
Client calls POST {url}/DataProcessor/DataX with some payload
Client checks on the status of the transaction using GET {url}/DataProcessor/DataXStatus, until HTTP Status Code of 200 is returned
Client gets xActionId from the response above and then calls GET {url}/DataProcessor/GetResults/{xActionId} to retrieve results of data processing.
This is what my controller looks like: When I call DataXStatus method (after properly calling the DataX method), the _processData isn't in the right state, like it has gone out of scope after DataX method is done. What am I doing wrong? Is DataProcessorController object gone after any one method is complete? What is the right way to do this?
Thanks in advance
[Route("[controller]")]
[ApiController]
public class DataProcessorController : ControllerBase
{
private ProcessData _processData = new ProcessData() ;
[HttpPost]
[Route("DataX")]
public IActionResult DataX([FromBody] xData value)
{
_processData.CalculateResult(value);
return Accepted();
}
[HttpGet]
[Route("DataXStatus")]
public IActionResult DataXStatus()
{
if(_processData.IsRunning())
{
return Accepted();
}
return Ok(new xDataStatus(){id = _processData.GetxActionId()});
}
[HttpGet]
[Route("GetResults/{xActionId}")]
public IActionResult GetResults(string xActionId)
{
return Ok(new xDataResults(){resX = _processData.GetResults()});
}
}
Answering this on my mobile so please forgive any typos.
Basically, yes the class is re-instaintaited on every request. The api does not keep context of previous requests unless the object you are keeping track of is static, which being me to my first solution for this:
Quick and dirty option:
I would recommend you use dependency injection for this instead. That gives you the option to run your 'proccessData' class as a singleton. Which basically means it's static.
Better option:
The other more correct way is to use a known library for this. There are are tons of libraries that are built for handling async requests in web apis. I think you should use hangfire for this, it takes a few minutes to set up and also has tons of configurion options.
https://docs.hangfire.io/en/latest/getting-started/aspnet-core-applications.html
Let me know if you need any further help!

What is the proper way of getting the HTTP request method in an AuthorizationHandler<T>?

Simplified version of what I'm trying to do: I want to write an authorization policy that will block requests that aren't GET if a flag is set in the database. I registered my policy, the handler code runs fine for my controller, but I'm not sure what the best way of getting the HTTP method type is.
Controller looks like this:
[Authorize(Policy = "Test")]
public class MyController : ControllerBase
{
// ...
}
My handler looks like this:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext ctx, MyRequirement req)
{
// Fetch some flag from db
if (!flag)
{
ctx.Succeed(req);
return;
}
var method = GetRequestMethodFromCtx(ctx.Resource);
if (method == HttpMethods.Get)
{
ctx.Succeed(req);
}
else
{
ctx.Fail();
}
}
I noticed that for a single request my handler gets called multiple times and that ctx.Resource is not always the same type. First time it is a RouteEndpoint, after which it is an AuthorizationFilterContextSealed.
Should I just extract the HTTP method from the RouteEndpoint and ignore the second call? Also, why are there multiple calls to the handler?
In order to access to HttpContext object you can use the IHttpContextAccessor service.
You can simply require it as a dependency of your authorization handler class and the ASP.NET core dependency injection will provide the service for you.
In order to register it with the dependency injection you need to call services.AddHttpContextAccessor() in the ConfigureServices method of your Startup class.
From the HttpContext you will have access to the Request and then to the Method property (see here).
Refer to the official documentation for the details.
I don't know exactly your requirements, but in order to prevent an action method being called with a specific HTTP verb there is a much simpler way. You just need to use the built in routing attributes, you can find more information here.

Preventing multiple switch statements

I'm currently writing a custom logging method for my Web API where users can purchase items. This log method will log all the steps the users takes while following a purchase process so support can later track the steps. There are multiple steps like:
Creating a order
Updating a order
Purchasing a item
Receiving status
etc..
This method will return one 'Event' object where all the necessary log information is combined, for example the LogLevel, Message, UserId and more, and write this to a database.
Before i can reach this point, i have to create the very useful log message. The message is based on these two enums (explanation is a bit simplified):
ActionName - At which step in my process is this log event called
ActionOrigin - Is the recorded log event from my front end or backend system...
It is also based on a object where the necessary log values, like order id for example, are provided.
The log class where the log event method is defined is a scoped injected class so i can log events every where critical in my code.
The first thing that came into my mind was creating a switch statement and create the messages based on the correct case. But this would combine 2 switch statements and quickly started to look like a mess.
I did some research and found the strategy pattern. I'm not completely sure if this can help me? Are there any other ideas or examples?
Whenever you are working on an object model and find yourself writing a ton of switch statements, it usually means you've put the class-specific logic in the wrong place. You should put it with the class itself, not the class that consumes it.
To put it another way, your logger should not know how to log each and every type of event. That would be a maintenance nightmare. Instead, it should know how to log a common object (e.g. a string), and each event should itself know how to create that common object, via a common method that the logger knows about. That is the only thing it needs to know.
Here is a simple example. In this case, the logger accepts any type of LoggableEvent and calls its Serialize() method to figure out how it gets added to the common log. The event itself is responsible for knowing how to serialize itself.
abstract class LoggableEventBase
{
public string ActionName { get; }
public string ActionOrigin { get; }
public LoggableEventBase(string actionName, string actionOrigin)
{
ActionName = actionName;
ActionOrigin = actionOrigin;
}
public virtual string Serialize()
{
return string.Format("{0} {1}", ActionName, ActionOrigin);
}
}
class CreateOrderEvent : LoggableEventBase
{
protected readonly List<Item> _items;
protected readonly int _orderId;
public CreateOrderEvent(string origin, int orderID, List<Item> items) : base("CreateOrder", origin)
{
_orderId = orderID;
_items = items;
}
public override string Serialize()
{
return base.Serialize() + string.Format(" {0} {1}", _orderId, string.Join(",", _items.Select(item => item.SKU)));
}
}
Now the actual logging logic is rather simple-- no switch statements or anything else that needs to know what the event is:
class Logger : ILogger
{
public void Log(LoggableEventBase eventToLog)
{
Write(eventToLog.Serialize());
}
protected virtual void Write(string message)
{
//Write the message to a log file
}
}
To add additional event types, you just need to define the new class (and override Serialize()). You never have to go back and modify the Logger class. This is more consistent with the Open-Closed Principle than your existing solution.
This is a design pattern question. You might want to read on different patterns used for the language/framework you are using. It seems like you are trying to avoid writing your logs in line. One way of doing it would be to define the format for your different messages in a constant and use string interpolation (or simple concatenation) to build the message with a log() method.
Example (I'll do my best to write proper C#, please edit any mistakes or inadequacies):
class Logger {
// If you want personalized messages for different actions or origins, define their template constants and create different methods for building them.
public const string ORDER_PROGRESS_MSG_TMPL = "Action:{0}, Origin:{1}, OrderId:{3}";
void log_order_progress(string actionName, sting actionOrigin, string orderId){
Console.WriteLine(
ORDER_PROGRESS_MSG_TMPL, actionName, actionOrigin, orderId
);
}
}
Order
class Order {
...
void create(int orederId){
Logger.log_order_progress(ActionEnum.CREATING, OriginEnum.BACK_END, orderId)
// Do some stuff here to create order
Logger.log_order_progress(ActionEnum.UPDATING, OriginEnum.BACK_END, orderId)
// etc
}
}
This is a way of doing it, you could modularize it more by having templates in their own class. Also you could create (or better: use an existing logging framework) to differentiate level of logging (debug, info, error) as #Sandeep Sharma described.
You can create multiple methods in your Logger class, each for specific scenario.
The methods can be :
info() = for logging some information.
debug() = for debugging.
error() = for logging an error event.
Let's say you want to log an event of purchasing an item , and when user does buy action, you can pass information to the logger.info() method.
If you encounter an error, or a certain action or condition was not fulfilled , you can pass data to the method error() , which will log error in your case.
For messages :
1. Action Name - You can pass the method name or route path that was called by action of an user.
2. Action Origin - Provide details like user name , full path , action type etc.
You can also maintain fields like 'timestamp' and some 'unique-identifier' for better logging of events.

Aspect Oriented Programming in ASP.NET MVC

Im currently developing an MVC application in ASP.NET and I'm trying to separate concerns so that I end up with cleaner and more maintanable code.
So, as a starting point, I was thinking of a logging aspect.
My idea is to log (initially) the calling and returning of every method in every controller.
I would have this logic on a separate class, dedicated to logging, so I don't mess my code with logging statements everywhere.
I would also need to have access to the Http Request so I can obtain client information.
Is there an integrated way to do this? Can ASP.NET MVC be used with aspect files just like AspectJ in Java?
Also, can it later be configured to log methods that satisfies certain conditions? (like signature, returning value, exception throwing, etc.)
Thanks very much in advance!
You can use attributes to implement a feature in an aspect-oriented way. Action methods that you want to surround with your functionality then only need to be decorated with your attribute:
[CustomLogger]
public ActionResult Index()
{
// Doing something here ...
return View();
}
You can either decorate a single action method with an attribute, an entire controller, or even apply the attribute globally through ASP.NET MVC's GlobalFilterCollection.
Here's how you'd declare your attribute:
public class CustomLoggerAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
// Here goes your logic
}
// ...
}
The ActionFilterAttribute class allows you to override a couple of methods so you can hook into ASP.NET MVC's action execution pipeline:
OnActionExecuting
OnActionExecuted
OnResultExecuting
OnResultExecuted
You can access request variables through the parameters (like ActionExecutedContext) that are passed to the above methods.

Cancel Output Caching in ChildAction

Does anyone know if it's possible to cancel output caching in code? What I mean is if I place output caching on a child action as follows can I then based on a condition cancel that caching from inside the child action?
[ChildActionOnly]
[OutputCache(Duration = 36000, VaryByParam="tagslug")]
public virtual ActionResult MostViewed(string tagslug, int count)
{
// Make an API call here. If not data returned do not cache the ChildAction as specified above
}
Skimming the framework source it looks like the only logic is don't-cache-on-exception:
// Only cache output if this wasn't an error
if (!wasException) {
ChildActionCacheInternal.Add(uniqueId, capturedText,
DateTimeOffset.UtcNow.AddSeconds(Duration));
}
I can't see a brilliant way to solve this: I think you'll have to make your own custom OutputCachingAttribute based on the original source from the ASP.NET MVC source from CodePlex, and then either add an extra check to that line for the output returned e.g.
if (!(wasException || capturedText.Contains("results:0"))) {
or similar, or find a way to pass that code a flag to this from your controller. The existing code uses an object to store a value on the session; you could copy this, e.g.
define a new static object key the same as _childActionFilterFinishCallbackKey e.g. _noCacheResultKey
add a public static method to the attribute that you can call e.g.
public static void FlagNoCache(HttpContext httpContext) {
httpContext.Items[_noCacheResultKey] = true;
}
extend ClearChildActionFilterFinishCallback to remove this from .Items[] as well as the callback
extend the above test to check this too e.g.
if (!(wasException
|| filterContext.HttpContext.Items.ContainsKey(_noCacheResultKey))) {
from your controller call MyOutputCacheAttribute.FlagNoCache(Context); as necessary.
It may also be possible to throw an exception from your code and then catch it in a different IExceptionFilter so that it doesn't get passed up beyond the OutputCacheAttribute but I don't know how sorry.

Categories

Resources