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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,13 @@ This approach ensures that performance metrics reflect real-world usage scenario

**Make sure to fill in the `RELEVANT_ARTICLES` with the ones you classify as relevant, so that you can compare the accuracy after running the `eval` script.***

## Sentry

- The application will automatically pick up and use environment variables if they are present in your environment or `.env` file.
- To enable Sentry error monitoring, set the `SENTRY_DSN` environment variable. This is **mandatory** for Sentry to be enabled. If `SENTRY_DSN` is not set, Sentry will be skipped and the application will run normally.
- If Sentry fails to initialize for any reason (e.g., network issues, invalid DSN), the application will log a warning and continue execution without error monitoring.
- Sentry is **optional**: the application does not require it to function, and all features will work even if Sentry is not configured or fails to start.

## 📄 License

This project is licensed under the [MIT License](LICENSE) - see the LICENSE file for details.
Expand Down
6 changes: 3 additions & 3 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ test-all: venv

# Format all code in the project.
format: venv
{{ run }} ruff check {{ target_dirs }}
{{ run }} ruff format {{ target_dirs }}

# Lint all code in the project.
lint: venv
{{ run }} ruff check {{ target_dirs }}
{{ run }} mypy {{ target_dirs }}
{{ run }} ruff check {{ target_dirs }}
{{ run }} mypy {{ target_dirs }}
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies = [
"ruff>=0.12.4",
"pydantic-ai-slim[google,openai]>=0.4.4",
"logfire>=3.25.0",
"sentry-sdk>=2.21.0,<3.0.0",
]
name = "lightman_ai"
version = "0.17.0"
Expand Down
2 changes: 2 additions & 0 deletions src/lightman_ai/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from lightman_ai.constants import DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_SECTION, DEFAULT_ENV_FILE
from lightman_ai.core.config import FileConfig, FinalConfig, PromptConfig
from lightman_ai.core.exceptions import ConfigNotFoundError, InvalidConfigError, PromptNotFoundError
from lightman_ai.core.sentry import configure_sentry
from lightman_ai.main import lightman

logger = logging.getLogger("lightman")
Expand Down Expand Up @@ -74,6 +75,7 @@ def run(
Holds no logic. It calls the main method and returns 0 when succesful .
"""
load_dotenv(env_file)
configure_sentry()
try:
prompt_config = PromptConfig.get_config_from_file(path=prompt_file)
config_from_file = FileConfig.get_config_from_file(config_section=config, path=config_file)
Expand Down
28 changes: 28 additions & 0 deletions src/lightman_ai/core/sentry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import logging
import os
from importlib import metadata

import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration


def configure_sentry() -> None:
"""Configure Sentry for error tracking and performance monitoring using env vars with fallbacks."""
try:
if not os.getenv("SENTRY_DSN"):
logging.getLogger("lightman").info("SENTRY_DSN not configured, skipping Sentry initialization")
return

# Logging level from ENV
logging_level_str = os.getenv("LOGGING_LEVEL", "ERROR").upper()
logging_level = getattr(logging, logging_level_str, logging.ERROR)

# Set up logging integration
sentry_logging = LoggingIntegration(level=logging.INFO, event_level=logging_level)

sentry_sdk.init(
release=metadata.version("lightman-ai"),
integrations=[sentry_logging],
)
except Exception as e:
logging.getLogger("lightman").warning("Could not instantiate Sentry! %s.\nContinuing with the execution.", e)
60 changes: 60 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import pytest
from lightman_ai.article.models import SelectedArticle, SelectedArticlesList
from lightman_ai.core.sentry import configure_sentry
from lightman_ai.main import _create_service_desk_issues, lightman
from tests.utils import patch_agent

Expand Down Expand Up @@ -174,3 +175,62 @@ def test_create_service_desk_issues_all_failures(
"Could not create ServiceDesk issue: New Attack Vector Discovered, https://example.com/article2"
in caplog.text
)


class TestSentryIntegration:
"""Tests for Sentry integration behavior."""

@patch.dict("os.environ", {}, clear=True) # Clear all env vars
def test_sentry_skipped_when_dsn_not_set(self, caplog: pytest.LogCaptureFixture) -> None:
"""Test that Sentry initialization is skipped when SENTRY_DSN is not set."""
with caplog.at_level(logging.INFO):
configure_sentry()

# Should log that Sentry is skipped
assert "SENTRY_DSN not configured, skipping Sentry initialization" in caplog.text

@patch.dict("os.environ", {"SENTRY_DSN": "https://test@sentry.io/123"})
@patch("lightman_ai.core.sentry.sentry_sdk.init")
def test_sentry_execution_continues_when_init_fails(
self, mock_sentry_init: Mock, caplog: pytest.LogCaptureFixture
) -> None:
"""Test that execution continues when Sentry initialization fails."""
# Make sentry_sdk.init raise an exception
mock_sentry_init.side_effect = Exception("Sentry connection failed")

with caplog.at_level(logging.WARNING):
# This should not raise an exception
configure_sentry()

# Should log the warning and continue
assert "Could not instantiate Sentry! Sentry connection failed" in caplog.text
assert "Continuing with the execution" in caplog.text

# Verify that sentry_sdk.init was called (and failed)
mock_sentry_init.assert_called_once()

@patch.dict("os.environ", {"SENTRY_DSN": "https://test@sentry.io/123"})
@patch("lightman_ai.core.sentry.sentry_sdk.init")
@patch("lightman_ai.core.sentry.metadata.version")
def test_sentry_initializes_successfully(
self, mock_version: Mock, mock_sentry_init: Mock, caplog: pytest.LogCaptureFixture
) -> None:
"""Test that Sentry initializes successfully when configured properly."""
# Mock the version lookup
mock_version.return_value = "1.0.0"

with caplog.at_level(logging.INFO):
configure_sentry()

# Should not log any warnings or errors
assert "Could not instantiate Sentry" not in caplog.text
assert "SENTRY_DSN not configured" not in caplog.text

# Verify that sentry_sdk.init was called with expected parameters
mock_sentry_init.assert_called_once()
call_kwargs = mock_sentry_init.call_args.kwargs
assert "release" in call_kwargs
assert "integrations" in call_kwargs

# Verify version was looked up
mock_version.assert_called_once_with("lightman-ai")
17 changes: 16 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading