File size: 3,212 Bytes
d53dc44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""

monitoring/logger.py — Structured JSON logging with file and console handlers.

All log entries are structured JSON for machine parsing.

"""
from __future__ import annotations

import json
import logging
import os
import sys
from datetime import datetime, timezone
from pathlib import Path

import config

LOG_DIR = Path(config.BASE_DIR) / "logs"
LOG_DIR.mkdir(parents=True, exist_ok=True)


class JSONFormatter(logging.Formatter):
    """Structured JSON log formatter."""

    def format(self, record: logging.LogRecord) -> str:
        log_entry = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno,
        }
        if record.exc_info and record.exc_info[1]:
            log_entry["exception"] = str(record.exc_info[1])

        if config.DRY_RUN:
            log_entry["mode"] = "DRY_RUN"
        elif config.TRADING_MODE == "paper":
            log_entry["mode"] = "PAPER"
        else:
            log_entry["mode"] = config.TRADING_MODE.upper()

        return json.dumps(log_entry)


class ConsoleFormatter(logging.Formatter):
    """Colored console formatter."""

    COLORS = {
        "DEBUG": "\033[36m",
        "INFO": "\033[32m",
        "WARNING": "\033[33m",
        "ERROR": "\033[31m",
        "CRITICAL": "\033[1;31m",
    }
    RESET = "\033[0m"

    def format(self, record: logging.LogRecord) -> str:
        color = self.COLORS.get(record.levelname, "")
        prefix = "[DRY_RUN] " if config.DRY_RUN else ""
        return (
            f"{color}{record.levelname:8s}{self.RESET} "
            f"{prefix}{record.name}: {record.getMessage()}"
        )


def setup_logging(level: str = "INFO"):
    """Configure logging for the entire trading system."""
    root = logging.getLogger()
    root.setLevel(getattr(logging, level.upper(), logging.INFO))

    # Remove existing handlers
    root.handlers.clear()

    # Console handler
    console = logging.StreamHandler(sys.stdout)
    console.setFormatter(ConsoleFormatter())
    console.setLevel(logging.INFO)
    root.addHandler(console)

    # JSON file handler
    today = datetime.now().strftime("%Y-%m-%d")
    log_file = LOG_DIR / f"trading_{today}.jsonl"
    file_handler = logging.FileHandler(str(log_file), encoding="utf-8")
    file_handler.setFormatter(JSONFormatter())
    file_handler.setLevel(logging.DEBUG)
    root.addHandler(file_handler)

    # Error file handler
    error_file = LOG_DIR / f"errors_{today}.jsonl"
    error_handler = logging.FileHandler(str(error_file), encoding="utf-8")
    error_handler.setFormatter(JSONFormatter())
    error_handler.setLevel(logging.ERROR)
    root.addHandler(error_handler)

    logging.getLogger("urllib3").setLevel(logging.WARNING)
    logging.getLogger("websocket").setLevel(logging.WARNING)

    logger = logging.getLogger("trading_system")
    logger.info("Logging initialized: console + %s", log_file)
    return logger