Implementing Activity Logging with Custom Attributes

Implementing Activity Logging with Custom Attributes

Introduction

In any enterprise-level application, logging user actions is essential for both auditing and security purposes. The Contact Management Application leverages middleware and a custom ActivityLogAttribute to implement detailed activity logging. This approach ensures that each user action, along with related metadata like IP address, user agent, and HTTP method, is captured and stored for future reference.

In this article, we will cover:

  1. How Activity Logging Middleware works.
  2. The role of the ActivityLogAttribute in logging actions.
  3. Capturing and persisting important metadata like IP address, HTTP method, and user agent.
  4. How to store and manage these logs.

Part of the Series

This article is part of a series on building and understanding the Contact Management Application, a sample project showcasing clean architecture principles. Below are the other articles in the series:

  1. Clean Architecture: Introduction to the Project Structure
  2. Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging
  3. Clean Architecture: Validating Inputs with FluentValidation
  4. Clean Architecture: Dependency Injection Setup Across Layers
  5. Clean Architecture: Handling Authorization and Role-Based Access Control (RBAC)
  6. Clean Architecture: Implementing Activity Logging with Custom Attributes (You are here)
  7. Clean Architecture: Unit of Work Pattern and Its Role in Managing Transactions
  8. Clean Architecture: Using Dapper for Data Access and Repository Pattern
  9. Clean Architecture: Best Practices for Creating and Using DTOs in the API
  10. Clean Architecture: Error Handling and Exception Management in the API
  11. Clean Architecture: Dockerizing the .NET Core API and MS SQL Server
  12. Clean Architecture: Seeding Initial Data Using Docker Compose and SQL Scripts

1. Overview of Activity Logging Middleware

Activity Logging Middleware captures user activities by intercepting incoming HTTP requests and checking for the presence of a custom ActivityLogAttribute on the endpoint. If the attribute is present, the middleware logs detailed information about the request, such as:

  • User ID: Identifies who performed the action.
  • Activity: A description of the action (e.g., “Creating a new contact”).
  • Endpoint: The API route that was accessed.
  • HTTP method: The request type (e.g., GET, POST).
  • IP address: The origin of the request.
  • User agent: The client making the request.

2. Implementing the Activity Logging Middleware

The ActivityLoggingMiddleware captures and logs user actions using the ActivityLogAttribute attached to specific endpoints.

2.1 Activity Logging Middleware Implementation

public class ActivityLoggingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ActivityLoggingMiddleware> _logger;
    private readonly IServiceProvider _serviceProvider;

    public ActivityLoggingMiddleware(RequestDelegate next, ILogger<ActivityLoggingMiddleware> logger, IServiceProvider serviceProvider)
    {
        _next = next;
        _logger = logger;
        _serviceProvider = serviceProvider;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var endpoint = context.GetEndpoint();
        var activityAttribute = endpoint?.Metadata.GetMetadata<ActivityLogAttribute>();

        if (activityAttribute != null)
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var activityLogService = scope.ServiceProvider.GetRequiredService<IActivityLogService>();
                var userIdClaim = context.User.FindFirst("id");
                var userId = userIdClaim != null ? Guid.Parse(userIdClaim.Value) : Guid.Empty;
                var activityDescription = activityAttribute.ActivityDescription;
                var endpointPath = context.Request.Path;
                var httpMethod = context.Request.Method;
                var ipAddress = context.Connection.RemoteIpAddress?.ToString();
                var userAgent = context.Request.Headers["User-Agent"].ToString();

                var logEntry = new ActivityLogEntry
                {
                    UserId = userId,
                    Activity = activityDescription,
                    Endpoint = endpointPath,
                    HttpMethod = httpMethod,
                    Timestamp = DateTimeOffset.UtcNow,
                    IpAddress = ipAddress,
                    UserAgent = userAgent
                };

                await activityLogService.LogActivityAsync(logEntry);
            }
        }

        await _next(context);
    }
}

2.2 Middleware Flow

  1. Intercept the request: The middleware checks if the endpoint has an attached ActivityLogAttribute.
  2. Create a logging scope: A scoped instance of IActivityLogService is used to log the action.
  3. Capture user and request metadata: The middleware captures details like user ID, IP address, HTTP method, and user agent.
  4. Log the activity: A new ActivityLogEntry is created and stored.

3. ActivityLog Attribute for Describing Actions

The ActivityLogAttribute is used to mark specific actions that need to be logged. It carries a description of the action, such as “Creating a new contact” or “Updating contact details.”

3.1 Defining the ActivityLog Attribute

[AttributeUsage(AttributeTargets.Method)]
public class ActivityLogAttribute : Attribute
{
    public string ActivityDescription { get; }

    public ActivityLogAttribute(string activityDescription)
    {
        ActivityDescription = activityDescription;
    }
}

This attribute is attached to API methods to specify what action is being performed, and that description is then logged by the middleware.


4. Applying Activity Logging in the ContactPersonController

To enable logging for specific API actions, the ActivityLogAttribute is applied to the corresponding controller methods.

4.1 Example Controller with ActivityLogAttribute

[ApiController]
[Route("api/[controller]")]
public class ContactPersonController : ControllerBase
{
    private readonly IContactPersonService _contactPersonService;

    public ContactPersonController(IContactPersonService contactPersonService)
    {
        _contactPersonService = contactPersonService;
    }

    [HttpPost]
    [ActivityLog("Creating new Contact")]
    public async Task<IActionResult> Add(CreateContactPerson createContactPerson)
    {
        var createdContactPerson = await _contactPersonService.Add(createContactPerson);
        return CreatedAtAction(nameof(GetById), new { id = createdContactPerson.Id }, createdContactPerson);
    }

    [HttpPut]
    [ActivityLog("Updating Contact")]
    public async Task<IActionResult> Update(UpdateContactPerson updateContactPerson)
    {
        var updatedContactPerson = await _contactPersonService.Update(updateContactPerson);
        return Ok(updatedContactPerson);
    }

    [HttpDelete("{id}")]
    [ActivityLog("Deleting Contact")]
    public async Task<IActionResult> Delete(Guid id)
    {
        var deleted = await _contactPersonService.Delete(id);
        if (!deleted) return NotFound();
        return NoContent();
    }
}

In this example, each API method is annotated with the ActivityLogAttribute to capture and log the action, such as Creating, Updating, or Deleting a contact.


5. Storing Activity Logs

To persist the activity logs, a database or an external logging service can be used. In this case, the logs are stored in a database using an ActivityLogEntry entity.

5.1 ActivityLogEntry Entity

public class ActivityLogEntry
{
    public Guid UserId { get; set; }
    public required string Activity { get; set; }
    public required string Endpoint { get; set; }
    public required string HttpMethod { get; set; }
    public DateTimeOffset Timestamp { get; set; }
    public required string IpAddress { get; set; }
    public required string UserAgent { get; set; }
}

5.2 Example of Logging Activity


public class ActivityLogService : IActivityLogService
{
    private readonly IActivityLogRepository _activityLogRepository;

    public ActivityLogService(IActivityLogRepository activityLogRepository)
    {
        _activityLogRepository = activityLogRepository;
    }

    public async Task LogActivityAsync(ActivityLogEntry logEntry)
    {
        await _activityLogRepository.AddAsync(logEntry);
    }
}


The activity logs are stored via a service that adds the ActivityLogEntry to the database for future retrieval and auditing.


6. Benefits of Implementing Middleware-Based Activity Logging

Implementing activity logging through middleware and custom attributes offers several key benefits:

  • Transparency: All significant actions are logged, offering a clear trail of user behavior.
  • Security: Logs can be used to monitor and audit sensitive actions, such as creating or deleting resources.
  • Compliance: Ensures that the application meets auditing and regulatory requirements.
  • Automation: The use of middleware and attributes automates the logging process, ensuring consistent capture of activity without repetitive code.

Conclusion

In this article, we explored how to implement Activity Logging in the Contact Management Application using middleware and custom attributes. By using the ActivityLogAttribute and the ActivityLoggingMiddleware, the application automatically captures and logs significant actions, along with essential metadata like IP address, HTTP method, and user agent.

This logging system enhances both the security and transparency of the application, ensuring that all user actions are tracked for auditing and troubleshooting purposes.

For more details, check out the full project on GitHub:

GitHub Repository: Contact Management Application


Discover more from Nitin Singh

Subscribe to get the latest posts sent to your email.

8 Responses

  1. December 3, 2024

    […] Clean Architecture: Implementing Activity Logging with Custom Attributes […]

  2. December 3, 2024

    […] Clean Architecture: Implementing Activity Logging with Custom Attributes […]

  3. December 3, 2024

    […] Clean Architecture: Implementing Activity Logging with Custom Attributes […]

  4. December 3, 2024

    […] Clean Architecture: Implementing Activity Logging with Custom Attributes […]

  5. December 3, 2024

    […] Clean Architecture: Implementing Activity Logging with Custom Attributes […]

  6. December 5, 2024

    […] Clean Architecture: Implementing Activity Logging with Custom Attributes […]

  7. December 5, 2024

    […] Clean Architecture: Implementing Activity Logging with Custom Attributes […]

  8. December 16, 2024

    […] Clean Architecture: Implementing Activity Logging with Custom Attributes […]

Leave a Reply