Skip to content
This repository was archived by the owner on Jun 5, 2025. It is now read-only.

Commit 9c2c6d6

Browse files
authored
Merge pull request #406 from stacklok/issue-301
feat: add custom logger that includes the origin
2 parents 5abcc04 + 0bf2a09 commit 9c2c6d6

File tree

4 files changed

+22
-54
lines changed

4 files changed

+22
-54
lines changed

src/codegate/cli.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def __init__(self, config: UvicornConfig, server: Server):
3333
self._startup_complete = asyncio.Event()
3434
self._shutdown_event = asyncio.Event()
3535
self._should_exit = False
36-
self.logger = structlog.get_logger("codegate")
36+
self.logger = structlog.get_logger("codegate").bind(origin="generic_server")
3737

3838
async def serve(self) -> None:
3939
"""Start the uvicorn server and handle shutdown gracefully."""
@@ -84,8 +84,8 @@ async def cleanup(self) -> None:
8484

8585
def validate_port(ctx: click.Context, param: click.Parameter, value: int) -> int:
8686
"""Validate the port number is in valid range."""
87-
logger = structlog.get_logger("codegate")
88-
logger.debug(f"Validating port number: {value}")
87+
cli_logger = structlog.get_logger("codegate").bind(origin="cli")
88+
cli_logger.debug(f"Validating port number: {value}")
8989
if value is not None and not (1 <= value <= 65535):
9090
raise click.BadParameter("Port must be between 1 and 65535")
9191
return value
@@ -296,7 +296,7 @@ def serve(
296296

297297
# Set up logging first
298298
setup_logging(cfg.log_level, cfg.log_format)
299-
logger = structlog.get_logger("codegate")
299+
logger = structlog.get_logger("codegate").bind(origin="cli")
300300

301301
init_db_sync(cfg.db_path)
302302

@@ -327,7 +327,7 @@ def serve(
327327
click.echo(f"Configuration error: {e}", err=True)
328328
sys.exit(1)
329329
except Exception as e:
330-
logger = structlog.get_logger("codegate")
330+
logger = structlog.get_logger("codegate").bind(origin="cli")
331331
logger.exception("Unexpected error occurred")
332332
click.echo(f"Error: {e}", err=True)
333333
sys.exit(1)
@@ -336,7 +336,7 @@ def serve(
336336
async def run_servers(cfg: Config, app) -> None:
337337
"""Run the codegate server."""
338338
try:
339-
logger = structlog.get_logger("codegate")
339+
logger = structlog.get_logger("codegate").bind(origin="cli")
340340
logger.info(
341341
"Starting server",
342342
extra={

src/codegate/codegate_logging.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,15 @@ def _missing_(cls, value: str) -> Optional["LogFormat"]:
4848
)
4949

5050

51+
def add_origin(logger, log_method, event_dict):
52+
# Add 'origin' if it's bound to the logger but not explicitly in the event dict
53+
if 'origin' not in event_dict and hasattr(logger, '_context'):
54+
origin = logger._context.get('origin')
55+
if origin:
56+
event_dict['origin'] = origin
57+
return event_dict
58+
59+
5160
def setup_logging(
5261
log_level: Optional[LogLevel] = None, log_format: Optional[LogFormat] = None
5362
) -> logging.Logger:
@@ -74,6 +83,7 @@ def setup_logging(
7483
shared_processors = [
7584
structlog.processors.add_log_level,
7685
structlog.processors.TimeStamper(fmt="%Y-%m-%dT%H:%M:%S.%03dZ", utc=True),
86+
add_origin,
7787
structlog.processors.CallsiteParameterAdder(
7888
[
7989
structlog.processors.CallsiteParameter.MODULE,

src/codegate/providers/copilot/provider.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import asyncio
22
import re
33
import ssl
4+
from src.codegate.codegate_logging import setup_logging
5+
import structlog
46
from dataclasses import dataclass
57
from typing import Dict, List, Optional, Tuple, Union
68
from urllib.parse import unquote, urljoin, urlparse
79

8-
import structlog
910
from litellm.types.utils import Delta, ModelResponse, StreamingChoices
1011

1112
from codegate.ca.codegate_ca import CertificateAuthority
@@ -22,7 +23,8 @@
2223
)
2324
from codegate.providers.copilot.streaming import SSEProcessor
2425

25-
logger = structlog.get_logger("codegate")
26+
setup_logging()
27+
logger = structlog.get_logger("codegate").bind(origin="copilot_proxy")
2628

2729
# Constants
2830
MAX_BUFFER_SIZE = 10 * 1024 * 1024 # 10MB
@@ -637,7 +639,7 @@ async def get_target_url(path: str) -> Optional[str]:
637639
# Check for prefix match
638640
for route in VALIDATED_ROUTES:
639641
# For prefix matches, keep the rest of the path
640-
remaining_path = path[len(route.path) :]
642+
remaining_path = path[len(route.path):]
641643
logger.debug(f"Remaining path: {remaining_path}")
642644
# Make sure we don't end up with double slashes
643645
if remaining_path and remaining_path.startswith("/"):
@@ -791,7 +793,7 @@ def data_received(self, data: bytes) -> None:
791793
self._proxy_transport_write(headers)
792794
logger.debug(f"Headers sent: {headers}")
793795

794-
data = data[header_end + 4 :]
796+
data = data[header_end + 4:]
795797

796798
self._process_chunk(data)
797799

tests/test_cli.py

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -190,28 +190,13 @@ def cli_runner():
190190
return CliRunner()
191191

192192

193-
@pytest.fixture
194-
def mock_logging(mocker):
195-
return mocker.patch("your_cli_module.structlog.get_logger")
196-
197-
198-
@pytest.fixture
199-
def mock_setup_logging(mocker):
200-
return mocker.patch("your_cli_module.setup_logging")
201-
202-
203193
def test_serve_default_options(cli_runner):
204194
"""Test serve command with default options."""
205195
# Use patches for run_servers and logging setup
206196
with (
207197
patch("src.codegate.cli.run_servers") as mock_run,
208-
patch("src.codegate.cli.structlog.get_logger") as mock_logging,
209198
patch("src.codegate.cli.setup_logging") as mock_setup_logging,
210199
):
211-
212-
logger_instance = MagicMock()
213-
mock_logging.return_value = logger_instance
214-
215200
# Invoke the CLI command
216201
result = cli_runner.invoke(cli, ["serve"])
217202

@@ -221,9 +206,6 @@ def test_serve_default_options(cli_runner):
221206
# Check if the logging setup was called with expected defaults
222207
mock_setup_logging.assert_called_once_with(LogLevel.INFO, LogFormat.JSON)
223208

224-
# Check if logging was done correctly
225-
mock_logging.assert_called_with("codegate")
226-
227209
# Validate run_servers was called once
228210
mock_run.assert_called_once()
229211

@@ -232,13 +214,8 @@ def test_serve_custom_options(cli_runner):
232214
"""Test serve command with custom options."""
233215
with (
234216
patch("src.codegate.cli.run_servers") as mock_run,
235-
patch("src.codegate.cli.structlog.get_logger") as mock_logging,
236217
patch("src.codegate.cli.setup_logging") as mock_setup_logging,
237218
):
238-
239-
logger_instance = MagicMock()
240-
mock_logging.return_value = logger_instance
241-
242219
# Invoke the CLI command with custom options
243220
result = cli_runner.invoke(
244221
cli,
@@ -271,9 +248,6 @@ def test_serve_custom_options(cli_runner):
271248
# Assert logging setup was called with the provided log level and format
272249
mock_setup_logging.assert_called_once_with(LogLevel.DEBUG, LogFormat.TEXT)
273250

274-
# Assert logger got called with the expected module name
275-
mock_logging.assert_called_with("codegate")
276-
277251
# Validate run_servers was called once
278252
mock_run.assert_called_once()
279253
# Retrieve the actual Config object passed to run_servers
@@ -332,20 +306,14 @@ def test_serve_with_config_file(cli_runner, temp_config_file):
332306
"""Test serve command with config file."""
333307
with (
334308
patch("src.codegate.cli.run_servers") as mock_run,
335-
patch("src.codegate.cli.structlog.get_logger") as mock_logging,
336309
patch("src.codegate.cli.setup_logging") as mock_setup_logging,
337310
):
338-
339-
logger_instance = MagicMock()
340-
mock_logging.return_value = logger_instance
341-
342311
# Invoke the CLI command with the configuration file
343312
result = cli_runner.invoke(cli, ["serve", "--config", str(temp_config_file)])
344313

345314
# Assertions to ensure the CLI ran successfully
346315
assert result.exit_code == 0
347316
mock_setup_logging.assert_called_once_with(LogLevel.DEBUG, LogFormat.JSON)
348-
mock_logging.assert_called_with("codegate")
349317

350318
# Validate that run_servers was called with the expected configuration
351319
mock_run.assert_called_once()
@@ -380,13 +348,8 @@ def test_serve_priority_resolution(cli_runner: CliRunner, temp_config_file: Path
380348
with (
381349
patch.dict(os.environ, {"LOG_LEVEL": "INFO", "PORT": "9999"}, clear=True),
382350
patch("src.codegate.cli.run_servers") as mock_run,
383-
patch("src.codegate.cli.structlog.get_logger") as mock_logging,
384351
patch("src.codegate.cli.setup_logging") as mock_setup_logging,
385352
):
386-
# Set up mock logger
387-
logger_instance = MagicMock()
388-
mock_logging.return_value = logger_instance
389-
390353
# Execute CLI command with specific options overriding environment and config file settings
391354
result = cli_runner.invoke(
392355
cli,
@@ -420,7 +383,6 @@ def test_serve_priority_resolution(cli_runner: CliRunner, temp_config_file: Path
420383

421384
# Ensure logging setup was called with the highest priority settings (CLI arguments)
422385
mock_setup_logging.assert_called_once_with("ERROR", "TEXT")
423-
mock_logging.assert_called_with("codegate")
424386

425387
# Verify that the run_servers was called with the overridden settings
426388
config_arg = mock_run.call_args[0][0] # Assuming Config is the first positional arg
@@ -448,13 +410,8 @@ def test_serve_certificate_options(cli_runner: CliRunner) -> None:
448410
"""Test serve command with certificate options."""
449411
with (
450412
patch("src.codegate.cli.run_servers") as mock_run,
451-
patch("src.codegate.cli.structlog.get_logger") as mock_logging,
452413
patch("src.codegate.cli.setup_logging") as mock_setup_logging,
453414
):
454-
# Set up mock logger
455-
logger_instance = MagicMock()
456-
mock_logging.return_value = logger_instance
457-
458415
# Execute CLI command with certificate options
459416
result = cli_runner.invoke(
460417
cli,
@@ -478,7 +435,6 @@ def test_serve_certificate_options(cli_runner: CliRunner) -> None:
478435

479436
# Ensure logging setup was called with expected arguments
480437
mock_setup_logging.assert_called_once_with("INFO", "JSON")
481-
mock_logging.assert_called_with("codegate")
482438

483439
# Verify that run_servers was called with the provided certificate options
484440
config_arg = mock_run.call_args[0][0] # Assuming Config is the first positional arg

0 commit comments

Comments
 (0)