Automatic Audit Logging Implementation

✅ Implementation Complete!

We’ve successfully implemented automatic audit logging using a Decorator Pattern combined with helper functions for the approval service.

What Was Implemented

1. Audit Helper Functions (audit_helper.go)

Simplified audit logging with pre-built helper functions:
auditHelper.LogApproval(ctx, ApprovalAuditParams{
    RequestID:    "req_123",
    ApproverID:   "user_456",
    ApproverName: "John Doe",
    Comment:      "Approved for vacation",
    // ... automatically captures all required fields
})
Available Helpers:
  • LogApproval() - Leave request approvals
  • LogRejection() - Leave request rejections
  • LogLeaveTypeCreation() - Leave type creation
  • LogLeaveTypeUpdate() - Leave type updates
  • LogLeaveTypeDeletion() - Leave type deletion
  • LogPolicyUpdate() - Policy changes

2. Audited Approval Service (audited_approval_service.go)

Decorator Pattern - wraps the original ApprovalService with automatic audit logging:
type AuditedApprovalService struct {
    inner       *ApprovalService  // Original service
    auditHelper *AuditHelper      // Audit logging
}

func (s *AuditedApprovalService) ApproveRequest(...) error {
    // 1. Capture BEFORE state
    request, _ := s.inner.leaveRequestRepo.FindByID(ctx, requestID)
    beforeStatus := request.Status

    // 2. Execute actual operation
    err := s.inner.ApproveRequest(...)

    // 3. Automatically log audit
    s.auditHelper.LogApproval(ctx, ...)

    return err
}
Key Features:
  • Automatic - No manual audit calls in business logic
  • Transparent - Original service unchanged
  • Captures failures - Logs even if operation fails
  • Non-blocking - Audit errors don’t fail the operation

3. Service Interface (approval_service_interface.go)

Created interface to allow using either raw or audited service:
type ApprovalServiceInterface interface {
    ApproveRequest(...) error
    RejectRequest(...) error
    GetTeamImpact(...) (*TeamImpact, error)
}

// Both implement the interface:
var _ ApprovalServiceInterface = (*ApprovalService)(nil)
var _ ApprovalServiceInterface = (*AuditedApprovalService)(nil)

4. Module Wiring (module.go)

Automatically uses audited service:
// Create base approval service
approvalService := services.NewApprovalService(...)

// Create audit helper
auditHelper := services.NewAuditHelper(auditService)

// Wrap with audit decorator
auditedApprovalService := services.NewAuditedApprovalService(
    approvalService,
    auditHelper,
)

// Handler uses audited service (automatic audit logging!)
approvalHandler := http.NewApprovalHandler(auditedApprovalService)

How It Works

Before (Manual Audit Logging):

func ApproveRequest(...) error {
    // Update request
    request.Status = "APPROVED"
    repo.Update(ctx, request)

    // ❌ MUST REMEMBER to log audit
    auditService.CreateAuditLog(ctx, ...)

    return nil
}
Problems:
  • ❌ Easy to forget
  • ❌ Repetitive code
  • ❌ Mixed concerns

After (Automatic Audit Logging):

// Original service - NO CHANGES!
func (s *ApprovalService) ApproveRequest(...) error {
    request.Status = "APPROVED"
    repo.Update(ctx, request)
    return nil
}

// Decorator automatically logs
func (s *AuditedApprovalService) ApproveRequest(...) error {
    // ✅ Automatically captures BEFORE state
    // ✅ Calls original service
    // ✅ Automatically logs audit
    // ✅ Returns result
}
Benefits:
  • ✅ Automatic - can’t forget
  • ✅ Clean separation of concerns
  • ✅ Original service unchanged
  • ✅ Easy to enable/disable

What Gets Audited Automatically

Approval Service (✅ Implemented):

  • Approve Request
    • Before/after status
    • Approver details (ID, name, role)
    • Comment
    • IP address, user agent
  • Reject Request
    • Before/after status
    • Rejection reason
    • Suggested alternative dates
    • Approver details

Configuration Service (📝 Ready to implement):

Use the helper functions:
// In ConfigurationService.CreateLeaveType()
auditHelper.LogLeaveTypeCreation(ctx, LeaveTypeAuditParams{
    LeaveTypeID: leaveType.ID,
    UserID:      params.CreatedBy,
    Name:        params.Name,
    Code:        params.Code,
    MaxDays:     params.MaxDays,
    // ... helper captures everything
})

Audit Log Data Structure

Each audit log entry contains:
{
  "id": "audit_01HKG...",
  "timestamp": "2025-12-26T11:00:00Z",
  "user_id": "user_456",
  "user_name": "John Doe",
  "user_role": "manager",
  "action": "APPROVE",
  "entity_type": "LEAVE_REQUEST",
  "entity_id": "req_123",
  "before": {
    "status": "PENDING"
  },
  "after": {
    "status": "APPROVED",
    "approver_id": "user_456"
  },
  "details": {
    "comment": "Approved for vacation",
    "total_days": 5
  },
  "ip_address": "192.168.1.1",
  "user_agent": "Mozilla/5.0...",
  "session_id": "sess_789",
  "request_id": "trace_abc"
}


✅ Context Extraction & Middleware (COMPLETED)

All context extraction features have been implemented and are working:

1. Context Helper Functions (✅ Implemented)

// In audited_approval_service.go - All working!
func getApproverRole(ctx context.Context) string {
    return middleware.GetUserRoleFromContext(ctx)
}

func getIPFromContext(ctx context.Context) string {
    return middleware.GetClientIP(ctx)
}

func getUserAgentFromContext(ctx context.Context) string {
    return middleware.GetUserAgent(ctx)
}

func getSessionIDFromContext(ctx context.Context) string {
    return middleware.GetSessionID(ctx)
}

func getTraceIDFromContext(ctx context.Context) string {
    return middleware.GetTraceID(ctx)
}

2. Audit Context Middleware (✅ Implemented)

File: middleware/audit_context.go
  • ✅ Captures client IP address
  • ✅ Captures user agent
  • ✅ Generates/extracts session ID
  • ✅ Generates trace ID
  • ✅ Fetches real user name from user service
  • ✅ Registered in leave module routes

3. Microservice-Ready Architecture (✅ Implemented)

  • ✅ Shared kernel interface (core/ports/user_lookup.go)
  • ✅ Anti-corruption layer (user/adapters/user_lookup_adapter.go)
  • ✅ Modules are decoupled
  • ✅ Can swap implementations (gRPC, LDAP, etc.)

Query Audit Logs

# Get all approvals by a manager
GET /api/v1/leave/audit?action=APPROVE&user_id=user_456

# Get all changes to a specific leave request
GET /api/v1/leave/audit?entity_type=LEAVE_REQUEST&entity_id=req_123

# Get all actions in a date range
GET /api/v1/leave/audit?start_date=2025-12-01&end_date=2025-12-31

Testing

Test Automatic Audit Logging:

# 1. Approve a leave request
POST /api/v1/leave/approvals/req_123/approve
{
  "comment": "Approved for vacation"
}

# 2. Check audit log was created automatically
GET /api/v1/leave/audit?entity_id=req_123

# Expected: Audit log entry with:
# - action: "APPROVE"
# - user_name: "John Doe" (real name from database!)
# - ip_address, user_agent, session_id, trace_id all populated
# - before: {status: "PENDING"}
# - after: {status: "APPROVED"}
# - details: {comment: "Approved for vacation"}

Summary

Automatic audit logging implemented for approval service
Helper functions for easy manual logging
Decorator pattern for transparent audit logging
Interface-based design for flexibility
Non-blocking - audit errors don’t fail operations
Comprehensive - captures before/after states, user context, IP, etc.
Context extraction - IP, user agent, session ID, trace ID all working
User name lookup - Real names from database
Microservice-ready - Decoupled architecture with shared kernel
Build Status: ✅ Success - No Errors! The approval service now automatically logs all approve/reject operations with full context to the audit log!