# Multi-Tenant Logging System

## 📋 Description

Logging system that automatically separates logs by context (admin, suitex, tenants) without needing to modify existing code. Logs are written to specific files based on execution context.

---

## 📂 Log Structure

```
storage/logs/
├── admin-2025-10-15.log      # Daily logs from admin.suitex
├── suitex-2025-10-15.log     # Daily logs from suitex.com
├── system-2025-10-15.log     # Daily logs without defined context
└── tenants/
    ├── tenant_123/
    │   ├── 2025-10-15.log    # Daily logs for tenant sx_db_123
    │   ├── 2025-10-14.log
    │   └── ...
    ├── tenant_456/
    │   └── 2025-10-15.log
    └── ...
```

**Advantages:**
- No duplication in `laravel.log` → saves disk space
- Easy traceability per tenant
- Daily rotation → easier to find specific errors by date
- Organized by tenant directory → simpler management
- Works automatically with existing code

---

## 🏗️ Architecture

### 1. **TenantContextProcessor** (`src/App/Logging/TenantContextProcessor.php`)

**Function:** Automatically detects execution context.

**Detection Priority:**
1. **Database:** Reads `config('database.connections.tenant_connection.database')`
   - If `sx_db_*` → TENANT context
2. **HTTP Request:** Analyzes hostname
   - `admin.suitex.*` → ADMIN context
   - `suitex.com` → SUITEX context
   - `{subdomain}.suitex.*` → TENANT context (uses subdomain as ID)
3. **No context:** → system (system.log)

**Cache:** Context is cached per request for efficiency.

---

### 2. **DynamicTenantHandler** (`src/App/Logging/DynamicTenantHandler.php`)

**Function:** Monolog handler that writes logs to dynamic files based on context.

**Process:**
1. Receives a `LogRecord`
2. Applies `TenantContextProcessor` to add context
3. Determines file path based on context
4. Creates directory if it doesn't exist
5. Writes to corresponding file

**Features:**
- Maintains open streams for efficiency
- Automatically closes streams on destruction
- Thread-safe for concurrent writing
- Removes sensitive context info (context, identifier, tenant_db) from log output

---

### 3. **TenantLogger** (`src/App/Logging/TenantLogger.php`)

**Function:** Factory that creates logger with custom handler.

**Configuration:**
```php
// config/logging.php
'tenant_aware' => [
    'driver' => 'custom',
    'via' => \App\Logging\TenantLogger::class,
    'level' => env('LOG_LEVEL', 'debug'),
],
```

---

## 🔧 Usage

### Automatic Usage (Recommended)

The system works automatically with Laravel's standard calls:

```php
// Anywhere in the code
Log::info('Log message');
Log::error('Error occurred', ['context' => 'data']);
Log::debug('Debug info');
```

**The log is automatically written to the correct file based on:**
- If `setupTenantConnection('sx_db_123')` was called → `tenants/tenant_123/YYYY-MM-DD.log`
- If on `admin.suitex` → `admin-YYYY-MM-DD.log`
- If on `suitex.com` → `suitex-YYYY-MM-DD.log`
- No context → `system-YYYY-MM-DD.log`

---

### Usage in Jobs and Commands

For it to work correctly in Jobs/Commands, use the `setupTenantConnection()` helper:

```php
// In a Job
public function handle()
{
    setupTenantConnection($this->tenantDatabase); // e.g., 'sx_db_123'
    
    Log::info('Processing tenant'); // → logs/tenants/tenant_123/YYYY-MM-DD.log
    
    // ... your code ...
}
```

**Example with multiple tenants:**

```php
// In a Command
public function handle()
{
    $tenants = Instance::all();
    
    foreach ($tenants as $tenant) {
        setupTenantConnection("sx_db_{$tenant->database}");
        
        Log::info("Processing tenant {$tenant->subdomain}");
        // → logs/tenants/tenant_{database}/YYYY-MM-DD.log
        
        // ... process tenant ...
    }
    
    setupTenantConnection(null); // Clear when done
}
```

---

## 🛠️ Helper: setupTenantConnection()

**Location:** `src/App/Helpers/helpers.php`

**Function:** Configures tenant database connection and logging context.

**Usage:**
```php
// Configure tenant
setupTenantConnection('sx_db_12345');

// Clear connection (return to main mysql)
setupTenantConnection(null);
```

**What it does internally:**
1. Configures `database.connections.tenant_connection.database`
2. Executes `DB::purge()` and `DB::reconnect()`
3. Sets connection as default with `DB::setDefaultConnection()`
4. **Automatically resets `TenantContextProcessor` cache** → logs go to correct file immediately

**Important:** No need to manually reset cache when switching tenants - it's automatic!

---

## 🎯 Future Modifications

### Add a new context

**1. Add constant in TenantContextProcessor:**
```php
const CONTEXT_NEW = 'new_context';
```

**2. Add detection logic in `detectContext()`:**
```php
// In detectContext() method
if ($someCondition) {
    return [
        'type' => self::CONTEXT_NEW,
        'identifier' => 'identifier',
    ];
}
```

**3. Add path in DynamicTenantHandler:**
```php
// In getLogPath() method
$date = date('Y-m-d');
TenantContextProcessor::CONTEXT_NEW => 
    storage_path("logs/new_context-{$date}.log"),
```

---

### Change log format

**Modify in TenantLogger:**
```php
private function getFormatter(): LineFormatter
{
    return new LineFormatter(
        "[%datetime%] %level_name%: %message% %context%\n", // Your format
        'Y-m-d H:i:s',
        true,
        true
    );
}
```

---

### Use different channels

```php
// Use specific channel
Log::channel('tenant_aware')->info('Message');

// Use multiple channels
Log::stack(['tenant_aware', 'slack'])->critical('Alert');
```

---

## 🧪 Tests

**Location:**
- `tests/Unit/Logging/TenantContextProcessorTest.php`
- `tests/Unit/Logging/TenantLoggerTest.php`
- `tests/Feature/Logging/TenantLoggingIntegrationTest.php`

**Run tests:**
```bash
php artisan test tests/Unit/Logging tests/Feature/Logging
```

---

## 🔍 Debug

### See which context is being used

```php
use App\Logging\TenantContextProcessor;

$processor = new TenantContextProcessor();
$record = new \Monolog\LogRecord(...);
$result = $processor($record);

dd($result->extra); // ['context' => 'tenant', 'identifier' => '123', ...]
```

### Force cache reset

```php
TenantContextProcessor::resetCache();
```

---

## ⚠️ Considerations

1. **Performance:** System caches context per request, no significant impact
2. **Disk Space:** Monitor `storage/logs/tenants/` in production
3. **Daily Rotation:** Logs rotate automatically daily (YYYY-MM-DD.log format)
4. **Old Logs:** Consider implementing automatic cleanup for logs older than X days
5. **Concurrency:** Handler is thread-safe, multiple processes can write simultaneously
6. **Privacy:** Sensitive tenant context (context, identifier, tenant_db) is automatically removed from log output

---

## 📚 References

- [Laravel Logging](https://laravel.com/docs/logging)
- [Monolog](https://github.com/Seldaek/monolog)
- `config/logging.php` - Channel configuration
