# Logging Documentation

This application uses [structlog](https://www.structlog.org/) for structured logging with JSON output support for containers and human-readable console output for development.

## Table of Contents

- [Overview](#overview)
- [Architecture](#architecture)
- [Configuration](#configuration)
- [Output Formats](#output-formats)
- [Usage Guide](#usage-guide)
- [Log Levels](#log-levels)
- [Best Practices](#best-practices)
- [Troubleshooting](#troubleshooting)

## Overview

The logging system automatically adapts to the runtime environment:

- **Container Environment**: JSON-formatted structured logs optimized for log aggregation tools (ELK, Splunk, CloudWatch, etc.)
- **Development Environment**: Human-readable console output with color-coding for easy debugging

### Key Features

- ✅ **Structured Logging**: Key-value pairs for easy parsing and filtering
- ✅ **JSON Output**: Automatic JSON formatting in container environments
- ✅ **Runtime Configuration**: Change log levels without restarting
- ✅ **Rich Metadata**: Automatic enrichment with timestamps, function names, line numbers
- ✅ **Auto-detection**: Automatically detects container environments
- ✅ **Backward Compatible**: Maintains compatibility with existing code

## Architecture

### Logging Flow Diagram

```mermaid
graph TB
    subgraph "Application Code"
        A[Application Module] -->|get_logger| B[Logger Instance]
    end
    
    subgraph "Structlog Configuration"
        B --> C{Environment Check}
        C -->|IN_CONTAINER=true| D[JSON Renderer]
        C -->|Development| E[Console Renderer]
    end
    
    subgraph "Processors Chain"
        D --> F[Add Contextvars]
        E --> F
        F --> G[Filter by Level]
        G --> H[Add Logger Name]
        H --> I[Add Log Level]
        I --> J[Add Timestamp]
        J --> K[Add Callsite Info]
        K --> L[Stack Info Renderer]
    end
    
    subgraph "Output"
        L --> M{Output Format}
        M -->|JSON| N[JSON Log Entry]
        M -->|Console| O[Colored Console Log]
    end
    
    N --> P[stdout]
    O --> P
```

### Configuration Flow

```mermaid
flowchart LR
    A[Application Startup] --> B{LOG_FORMAT set?}
    B -->|Yes| C[Use LOG_FORMAT]
    B -->|No| D{Check Environment}
    D -->|/.dockerenv exists| E[Use JSON]
    D -->|IN_CONTAINER=true| E
    D -->|Otherwise| F[Use Console]
    C --> G[Configure Structlog]
    E --> G
    F --> G
    G --> H{LOG_LEVEL set?}
    H -->|Yes| I[Use LOG_LEVEL]
    H -->|No| J[Use INFO default]
    I --> K[Logging Ready]
    J --> K
```

### Log Entry Processing Pipeline

```mermaid
sequenceDiagram
    participant App as Application Code
    participant Logger as Structlog Logger
    participant Proc as Processors
    participant Rend as Renderer
    participant Out as Output Stream
    
    App->>Logger: log.info("event", key=value)
    Logger->>Proc: Add contextvars
    Proc->>Proc: Filter by level
    Proc->>Proc: Add logger name
    Proc->>Proc: Add log level
    Proc->>Proc: Add timestamp (ISO)
    Proc->>Proc: Add callsite info (func, line, module)
    Proc->>Rend: Process with renderer
    alt JSON Format
        Rend->>Out: {"event": "...", "key": "value", ...}
    else Console Format
        Rend->>Out: [timestamp] [level] event [logger] key=value ...
    end
```

## Configuration

### Environment Variables

| Variable | Values | Default | Description |
|----------|--------|---------|-------------|
| `LOG_LEVEL` | DEBUG, INFO, WARNING, ERROR, CRITICAL | INFO | Controls log verbosity |
| `LOG_FORMAT` | json, console | Auto-detect | Forces specific output format |
| `IN_CONTAINER` | true, false | Auto-detect | Indicates container environment |

### Environment Detection

The system automatically detects container environments by checking:

1. `LOG_FORMAT` environment variable (highest priority)
2. `IN_CONTAINER` environment variable
3. Existence of `/.dockerenv` file (Docker containers)
4. Existence of `/run/.containerenv` file (Podman containers)

If none are detected, console format is used (development mode).

### Configuration Examples

#### Force JSON Output (Outside Container)
```bash
export LOG_FORMAT=json
export LOG_LEVEL=DEBUG
python -m maybankforme.api
```

#### Force Console Output (Inside Container)
```bash
docker run -e LOG_FORMAT=console -e LOG_LEVEL=INFO myapp
```

#### Debug Logging in Development
```bash
export LOG_LEVEL=DEBUG
uvicorn maybankforme.api:app --reload
```

## Output Formats

### JSON Format (Container Mode)

Perfect for log aggregation and analysis tools.

```json
{
  "event": "processing_started",
  "file_count": 5,
  "total_size_mb": 15.7,
  "logger": "maybankforme.api",
  "level": "info",
  "timestamp": "2025-10-25T20:30:45.123456Z",
  "func_name": "process_statements",
  "lineno": 245,
  "module": "api"
}
```

**Metadata Fields:**
- `event`: The log message or event name
- `logger`: Name of the logger (usually module name)
- `level`: Log level (debug, info, warning, error)
- `timestamp`: ISO 8601 formatted timestamp
- `func_name`: Function where log was called
- `lineno`: Line number in source code
- `module`: Python module name
- Custom fields: Any additional key-value pairs passed to the logger

### Console Format (Development Mode)

Human-readable with optional color-coding.

```
2025-10-25T20:30:45.123456Z [info     ] processing_started         [maybankforme.api] file_count=5 func_name=process_statements lineno=245 module=api total_size_mb=15.7
```

**Format Structure:**
```
[timestamp] [level] event_name [logger] key1=value1 key2=value2 ...
```

## Usage Guide

### Basic Usage

```python
from maybankforme.common.utils import get_logger

# Get a logger instance (typically at module level)
log = get_logger(__name__)

# Simple log message
log.info("operation_completed")

# Log with structured data
log.info("user_login", user_id=123, username="john", ip_address="192.168.1.1")

# Log errors with context
log.error(
    "database_connection_failed",
    error="Connection timeout",
    host="db.example.com",
    port=5432,
    retry_count=3
)

# Debug information
log.debug("cache_hit", key="user:123", ttl_seconds=300)
```

### Advanced Usage

#### Logging with Exception Information

```python
try:
    process_file(filename)
except Exception as e:
    log.error(
        "file_processing_failed",
        filename=filename,
        error=str(e),
        error_type=type(e).__name__,
        exc_info=True  # Includes full traceback
    )
```

#### Contextual Logging

```python
from maybankforme.common.utils import get_logger

log = get_logger(__name__)

def process_transaction(transaction_id, amount):
    log.info(
        "transaction_started",
        transaction_id=transaction_id,
        amount=amount,
        currency="MYR"
    )
    
    # Process...
    
    log.info(
        "transaction_completed",
        transaction_id=transaction_id,
        status="success",
        processing_time_ms=150
    )
```

#### Using Backward Compatible Logger

For existing code using `DockerSafeLogger`:

```python
from maybankforme.common.utils import DockerSafeLogger

log = DockerSafeLogger().logger

# Works exactly as before
log.info("message")
log.error("error", exc_info=True)
```

**Note:** New code should use `get_logger()` for better type safety and features.

## Log Levels

### Level Hierarchy

```mermaid
graph TD
    A[DEBUG - 10] -->|includes| B[INFO - 20]
    B -->|includes| C[WARNING - 30]
    C -->|includes| D[ERROR - 40]
    D -->|includes| E[CRITICAL - 50]
    
    style A fill:#e3f2fd
    style B fill:#c8e6c9
    style C fill:#fff9c4
    style D fill:#ffccbc
    style E fill:#f8bbd0
```

### When to Use Each Level

| Level | Use Case | Examples |
|-------|----------|----------|
| **DEBUG** | Detailed diagnostic information | Cache operations, database queries, variable states |
| **INFO** | General operational events | Requests received, files processed, tasks completed |
| **WARNING** | Unexpected but handled situations | High memory usage, deprecated features, retries |
| **ERROR** | Errors that need attention | Failed operations, connection errors, validation failures |
| **CRITICAL** | System-level failures | Service down, data corruption, fatal errors |

### Log Level Impact

When `LOG_LEVEL=INFO`:
- ✅ INFO, WARNING, ERROR, CRITICAL messages are logged
- ❌ DEBUG messages are filtered out

```mermaid
graph LR
    A[LOG_LEVEL=INFO] --> B{Filter Messages}
    B -->|Level < INFO| C[Discard]
    B -->|Level >= INFO| D[Process & Output]
    
    E[DEBUG] --> B
    F[INFO] --> B
    G[WARNING] --> B
    H[ERROR] --> B
```

## Best Practices

### 1. Use Structured Data

**Good:**
```python
log.info("user_created", user_id=123, email="user@example.com", role="admin")
```

**Avoid:**
```python
log.info(f"User {user_id} created with email {email} and role {role}")
```

### 2. Consistent Event Names

Use descriptive, snake_case event names:

```python
log.info("database_connection_established")
log.info("payment_processing_started")
log.error("external_api_timeout")
```

### 3. Include Relevant Context

Always include metadata that helps with debugging:

```python
log.error(
    "file_processing_error",
    filename=filename,
    file_size_bytes=file_size,
    error=str(e),
    error_type=type(e).__name__
)
```

### 4. Use Appropriate Log Levels

```python
# ✅ Correct usage
log.debug("cache_lookup", key=cache_key, hit=True)
log.info("request_processed", status_code=200, duration_ms=45)
log.warning("high_memory_usage", usage_percent=85, threshold=80)
log.error("database_error", error="Connection refused")

# ❌ Avoid
log.error("User logged in")  # Not an error
log.info("Failed to connect to database")  # Should be error
```

### 5. Don't Log Sensitive Information

```python
# ❌ Never log passwords, tokens, credit cards
log.info("user_login", username=username, password=password)  # BAD!

# ✅ Log safely
log.info("user_login", username=username, auth_method="password")
```

### 6. Performance Considerations

```python
# ✅ Efficient - only evaluated if DEBUG is enabled
log.debug("complex_operation", result=expensive_computation())

# For very expensive operations, check level first
if log.level <= logging.DEBUG:
    log.debug("data_dump", data=serialize_large_object())
```

## Troubleshooting

### Common Issues

#### 1. Logs Not Appearing

**Problem:** No log output visible

**Solutions:**
```bash
# Check log level
export LOG_LEVEL=DEBUG

# Check if logging is configured
python -c "from maybankforme.common.utils import configure_logging; configure_logging(); import structlog; print(structlog.is_configured())"
```

#### 2. Wrong Output Format

**Problem:** Getting JSON when expecting console output (or vice versa)

**Solutions:**
```bash
# Force console format
export LOG_FORMAT=console

# Force JSON format
export LOG_FORMAT=json

# Check environment detection
python -c "import os; print('/.dockerenv exists:', os.path.exists('/.dockerenv')); print('IN_CONTAINER:', os.getenv('IN_CONTAINER'))"
```

#### 3. Missing Metadata Fields

**Problem:** Log entries missing expected fields (func_name, lineno, etc.)

**Solution:** Ensure you're using `get_logger()` and not creating loggers directly:

```python
# ✅ Correct
from maybankforme.common.utils import get_logger
log = get_logger(__name__)

# ❌ Incorrect - bypasses structlog
import logging
log = logging.getLogger(__name__)
```

#### 4. Duplicate Log Entries

**Problem:** Each log appears multiple times

**Solution:** Check for multiple handler configurations. The application should call `configure_logging()` only once at startup.

### Debug Logging Configuration

To verify your logging setup:

```python
import os
import structlog
from maybankforme.common.utils import configure_logging, get_logger

# Show current environment
print("Environment Variables:")
print(f"  LOG_LEVEL: {os.getenv('LOG_LEVEL', 'INFO')}")
print(f"  LOG_FORMAT: {os.getenv('LOG_FORMAT', 'auto')}")
print(f"  IN_CONTAINER: {os.getenv('IN_CONTAINER', 'false')}")

# Configure and test
configure_logging()
print(f"\nStructlog configured: {structlog.is_configured()}")

# Test logging
log = get_logger("test")
log.debug("debug_test")
log.info("info_test")
log.warning("warning_test")
log.error("error_test")
```

## Docker Configuration

### Dockerfile Settings

The application's Dockerfile is pre-configured for JSON logging:

```dockerfile
ENV LOG_LEVEL=INFO \
    LOG_FORMAT=json \
    IN_CONTAINER=true
```

### Runtime Override

Override settings when running containers:

```bash
# Debug logging
docker run -e LOG_LEVEL=DEBUG myapp

# Console format in container (for debugging)
docker run -e LOG_FORMAT=console myapp

# Custom log level
docker run -e LOG_LEVEL=WARNING myapp
```

### Docker Compose

```yaml
services:
  app:
    image: myapp
    environment:
      - LOG_LEVEL=INFO
      - LOG_FORMAT=json
      - IN_CONTAINER=true
    # Logs will be available via docker-compose logs
```

## Integration with Log Management Tools

### ELK Stack (Elasticsearch, Logstash, Kibana)

JSON logs can be easily ingested into ELK:

```yaml
# logstash.conf
input {
  file {
    path => "/var/log/app/*.log"
    codec => json
  }
}

filter {
  # Logs are already in JSON, no parsing needed
  mutate {
    add_field => { "app" => "maybankforme" }
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "maybankforme-%{+YYYY.MM.dd}"
  }
}
```

### AWS CloudWatch

Configure CloudWatch to parse JSON logs:

```python
# The JSON format is automatically parsed by CloudWatch Logs Insights
# Example query:
fields @timestamp, event, level, logger, func_name
| filter level = "error"
| sort @timestamp desc
| limit 100
```

### Splunk

JSON logs integrate seamlessly with Splunk:

```
# Splunk search examples
index="maybankforme" level="error"
index="maybankforme" event="processing_failed" | stats count by filename
index="maybankforme" | timechart count by level
```

## Migration Guide

### From Standard Logging

If you have existing code using standard Python logging:

**Before:**
```python
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info(f"Processing file: {filename}")
logger.error(f"Failed to process {filename}: {error}")
```

**After:**
```python
from maybankforme.common.utils import get_logger

log = get_logger(__name__)

log.info("processing_file", filename=filename)
log.error("processing_failed", filename=filename, error=str(error))
```

### Benefits of Migration

- 🎯 **Structured data**: Easy to query and filter
- 📊 **Better analytics**: Aggregate and visualize in log management tools
- 🔍 **Easier debugging**: Find issues faster with structured queries
- 📈 **Performance**: JSON parsing is faster than regex-based log parsing
- 🔄 **Consistency**: Same format across all services

## References

- [structlog Documentation](https://www.structlog.org/)
- [Twelve-Factor App: Logs](https://12factor.net/logs)
- [Structured Logging Best Practices](https://www.structlog.org/en/stable/standard-library.html)
