Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 9 additions & 26 deletions plain2code.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@
PlainSyntaxError,
)
from plain2code_logger import (
LOGGER_NAME,
CrashLogHandler,
IndentedFormatter,
RetryOnlyFilter,
TuiLoggingHandler,
dump_crash_logs,
get_log_file_path,
Expand Down Expand Up @@ -110,20 +110,12 @@ def setup_logging(
log_to_file: bool,
log_file_name: str,
plain_file_path: Optional[str],
render_id: str,
headless: bool = False,
):
# Set default level to INFO for everything not explicitly configured
logging.getLogger().setLevel(logging.INFO)
logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logging.getLogger("anthropic").setLevel(logging.WARNING)
logging.getLogger("langsmith").setLevel(logging.WARNING)
logging.getLogger(LOGGER_NAME).setLevel(logging.INFO)
logging.getLogger("git").setLevel(logging.WARNING)
logging.getLogger("anthropic._base_client").setLevel(logging.WARNING)
logging.getLogger("services.langsmith.langsmith_service").setLevel(logging.WARNING)
logging.getLogger("repositories").setLevel(logging.WARNING)
logging.getLogger("transitions").setLevel(logging.ERROR)
logging.getLogger("transitions.extensions.diagrams").setLevel(logging.ERROR)

Expand All @@ -139,18 +131,11 @@ def setup_logging(
except Exception as e:
console.warning(f"Failed to load logging configuration from {args.logging_config_path}: {str(e)}")

# Allow detailed retry logs for anthropic if needed
logging.getLogger("anthropic._base_client").setLevel(logging.DEBUG)
if logging.getLogger("anthropic._base_client").level == logging.DEBUG:
logging.getLogger("anthropic._base_client").addFilter(RetryOnlyFilter())

# The IndentedFormatter provides better multiline log readability.
# We add the TuiLoggingHandler to the root logger.
# CRITICAL: We must remove existing handlers (like StreamHandler) to prevent double-logging
# that spills into the TUI dashboard.
root_logger = logging.getLogger()
for h in root_logger.handlers[:]:
root_logger.removeHandler(h)
root_logger = logging.getLogger(LOGGER_NAME)
configured_log_level = root_logger.level
root_logger.setLevel(logging.DEBUG) # Capture all logs; handlers will filter levels as needed

formatter = IndentedFormatter("%(levelname)s:%(name)s:%(message)s")

Expand All @@ -163,6 +148,7 @@ def setup_logging(
try:
file_handler = logging.FileHandler(log_file_path, mode="w")
file_handler.setFormatter(formatter)
file_handler.setLevel(configured_log_level)
root_logger.addHandler(file_handler)
except Exception as e:
console.warning(f"Failed to setup file logging to {log_file_path}: {str(e)}")
Expand All @@ -171,10 +157,9 @@ def setup_logging(
# in case we need to dump them on crash.
crash_handler = CrashLogHandler()
crash_handler.setFormatter(formatter)
crash_handler.setLevel(configured_log_level)
root_logger.addHandler(crash_handler)

root_logger.info(f"Render ID: {render_id}") # Ensure render ID is logged in to codeplain.log file


def _check_connection(codeplainAPI: codeplain_api.CodeplainAPI):
"""Check API connectivity and validate API key and client version."""
Expand Down Expand Up @@ -293,9 +278,7 @@ def main(): # noqa: C901
# Suppress Rich console output.
console.quiet = True

setup_logging(
args, event_bus, args.log_to_file, args.log_file_name, args.filename, run_state.render_id, args.headless
)
setup_logging(args, event_bus, args.log_to_file, args.log_file_name, args.filename, args.headless)

exc_info = None
try:
Expand All @@ -305,7 +288,7 @@ def main(): # noqa: C901
"API key is required. Please set the CODEPLAIN_API_KEY environment variable or provide it with the --api-key argument."
)

console.debug(f"Render ID: {run_state.render_id}") # Ensure render ID is logged to the console
console.info(f"Render ID: {run_state.render_id}")
render(args, run_state, event_bus)
except InvalidFridArgument as e:
exc_info = sys.exc_info()
Expand Down
2 changes: 1 addition & 1 deletion plain2code_arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def create_parser():

parser.add_argument(
"--logging-config-path",
action="store_true",
type=str,
default="logging_config.yaml",
help="Path to the logging configuration file.",
)
Expand Down
22 changes: 13 additions & 9 deletions plain2code_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
from rich.style import Style
from rich.tree import Tree

import plain2code_logger

CHARACTERS_TO_TOKENS_RULE_OF_THUMB_RATIO = 4

logger = logging.getLogger(plain2code_logger.LOGGER_NAME)


class Plain2CodeConsole(Console):
INFO_STYLE = Style()
Expand All @@ -22,35 +26,35 @@ def __init__(self):

self.llm_encoding = tiktoken.get_encoding("cl100k_base")
except Exception as e:
logging.warning(
logger.warning(
"Failed to import optional library tiktoken. Using approximate instead of exact token count."
)
logging.debug(f"Exception: {e}")
logger.debug(f"Exception: {e}")
self.llm_encoding = None

def info(self, *args, **kwargs):
logging.info(" ".join(map(str, args)))
logger.info(" ".join(map(str, args)))
super().print(*args, **kwargs, style=self.INFO_STYLE)

def warning(self, *args, **kwargs):
logging.warning(" ".join(map(str, args)))
logger.warning(" ".join(map(str, args)))
super().print(*args, **kwargs, style=self.WARNING_STYLE)

def error(self, *args, **kwargs):
logging.error(" ".join(map(str, args)))
logger.error(" ".join(map(str, args)))
super().print(*args, **kwargs, style=self.ERROR_STYLE)

def input(self, *args, **kwargs):
# We also log input as info so it shows in the toggled view
logging.info(" ".join(map(str, args)))
logger.info(" ".join(map(str, args)))
super().print(*args, **kwargs, style=self.INPUT_STYLE)

def output(self, *args, **kwargs):
logging.info(" ".join(map(str, args)))
logger.info(" ".join(map(str, args)))
super().print(*args, **kwargs, style=self.OUTPUT_STYLE)

def debug(self, *args, **kwargs):
logging.debug(" ".join(map(str, args)))
logger.debug(" ".join(map(str, args)))
super().print(*args, **kwargs, style=self.DEBUG_STYLE)

def print_list(self, items, style=None):
Expand Down Expand Up @@ -116,7 +120,7 @@ def _count_tokens(self, text):

def print_resources(self, resources_list, linked_resources):
if len(resources_list) == 0:
self.input("Linked resources: None")
self.debug("Linked resources: None")
return

self.input("Linked resources:")
Expand Down
2 changes: 1 addition & 1 deletion plain2code_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class RenderFailed(BaseEvent):

@dataclass
class LogMessageEmitted(BaseEvent):
logger_name: str # e.g., "services.langsmith.langsmith_service", "root"
logger_name: str # e.g., "codeplain"
level: str # e.g., "INFO", "DEBUG", "ERROR"
message: str # The actual log message
timestamp: str # Formatted timestamp
Expand Down
21 changes: 2 additions & 19 deletions plain2code_logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,7 @@
from event_bus import EventBus
from plain2code_events import LogMessageEmitted


class RetryOnlyFilter(logging.Filter):
def filter(self, record):
# Allow all logs with level > DEBUG (i.e., INFO and above)
if record.levelno > logging.DEBUG:
return True
# For DEBUG logs, only allow if message matches retry-related patterns
msg = record.getMessage().lower()
return (
"retrying due to" in msg
or "raising timeout error" in msg
or "raising connection error" in msg
or "encountered exception" in msg
or "retrying request" in msg
or "retry left" in msg
or "1 retry left" in msg
or "retries left" in msg
)
LOGGER_NAME = "codeplain"


class IndentedFormatter(logging.Formatter):
Expand Down Expand Up @@ -105,7 +88,7 @@ def dump_crash_logs(args, formatter=None):
if formatter is None:
formatter = IndentedFormatter("%(levelname)s:%(name)s:%(message)s")

root_logger = logging.getLogger()
root_logger = logging.getLogger(LOGGER_NAME)
crash_handler = next((h for h in root_logger.handlers if isinstance(h, CrashLogHandler)), None)

if crash_handler and args.filename:
Expand Down
5 changes: 0 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,8 @@ exclude = [
"^\\.venv/",
"^\\.env/",
"^\\.conda/",
"^build/",
"^dist/",
"^tests/",
"^examples/",
"^render_cache/",
"^src/services/langsmith/",
"^deploy/",
]

[tool.pytest.ini_options]
Expand Down
2 changes: 1 addition & 1 deletion tui/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ class StructuredLogView(VerticalScroll):

def __init__(self, **kwargs):
super().__init__(**kwargs)
self.min_level = "DEBUG" # Show all by default
self.min_level = "INFO" # By default, show INFO and above

def _should_show_log(self, level: str) -> bool:
"""Check if log should be shown based on minimum level."""
Expand Down
Loading