Skip to content
Snippets Groups Projects
Commit 6699c096 authored by Erik Reid's avatar Erik Reid
Browse files

added config import and sentry instrumentation

parent 22cb99d2
No related branches found
No related tags found
No related merge requests found
......@@ -5,12 +5,16 @@ Default entry point for the FastAPI application.
from fastapi import FastAPI
from mapping_provider.api import common, map
from mapping_provider import config
from mapping_provider import environment
def create_app() -> FastAPI:
"""
Creates the FastAPI application instance, with routers attached.
"""
environment.setup_logging()
environment.setup_sentry(config.load().sentry)
app = FastAPI(
title="Mapping provider",
description="Mapping provider endpoints for GÉANT maps",
......
......@@ -5,6 +5,8 @@ import requests
from fastapi import APIRouter
from pydantic import BaseModel
from mapping_provider import config
router = APIRouter()
......@@ -161,11 +163,11 @@ def get_sites() -> SiteList:
"""
handler for /sites
"""
# TODO: catch/handle the usual exceptions
app_params = config.load()
rv = requests.get(
f'{INPROV_API_URL_TODO}/map/sites',
f'{app_params.inventory}/map/sites',
headers={'Accept': 'application/json'})
rv.raise_for_status()
site_list_json = rv.json()
......@@ -183,8 +185,9 @@ def get_routers() -> RouterList:
# TODO: catch/handle the usual exceptions
app_params = config.load()
rv = requests.get(
f'{INPROV_API_URL_TODO}/map/routers',
f'{app_params.inventory}/map/routers',
headers={'Accept': 'application/json'})
rv.raise_for_status()
router_list_json = rv.json()
......@@ -202,8 +205,9 @@ def get_trunks() -> ServiceList:
# TODO: catch/handle the usual exceptions
app_params = config.load()
rv = requests.get(
f'{INPROV_API_URL_TODO}/map/services/IP TRUNK',
f'{app_params.inventory}/map/services/IP TRUNK',
headers={'Accept': 'application/json'})
rv.raise_for_status()
service_list_json = rv.json()
......
import os
from pydantic import BaseModel, Field, HttpUrl
class SentryConfig(BaseModel):
dsn: str
environment: str
level: str = Field(
default='error',
pattern="^(debug|info|warning|error)$")
# @field_validator('level')
# @classmethod
# def validate_level(cls, v):
# if v not in ['debug', 'info', 'warning', 'error']:
# raise ValueError('level must be one of: debug, info, warning, error')
# return v
class Configuration(BaseModel):
sentry: SentryConfig
inventory: HttpUrl
# CONFIG_SCHEMA = {
# '$schema': 'https://json-schema.org/draft/2020-12/schema',
# 'definitions': {
# 'sentry-info': {
# 'type': 'object',
# 'properties': {
# 'dsn': {'type': 'string'},
# 'environment': {'type': 'string'},
# 'level': {
# 'type': 'string',
# 'enum': ['debug', 'info', 'warning', 'error'],
# 'default': DEFAULT_SENTRY_LOG_LEVEL
# }
# },
# 'required': ['dsn'],
# 'additionalProperties': False,
# },
# },
# 'type': 'object',
# 'properties': {
# 'sentry': {'$ref': '#/definitions/sentry-info'},
# 'inventory': {
# 'type': 'string',
# 'format': 'uri',
# }
# },
# 'required': ['sentry', 'inventory'],
# 'additionalProperties': False
# }
def load():
"""
Loads, validates and returns configuration parameters from
the file named in the environment variable 'SETTINGS_FILENAME'.
:return:
"""
assert 'SETTINGS_FILENAME' in os.environ
with open(os.environ['SETTINGS_FILENAME']) as f:
return Configuration.model_validate_json(f.read())
"""
Environment setup
===================
.. autofunction:: mapping_provider.environment.setup_logging
.. autofunction:: mapping_provider.environment.setup_sentry
"""
import json
import logging.config
import logging
import os
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mapping_provider.config import SentryConfig
import sentry_sdk
from sentry_sdk.integrations.logging import LoggingIntegration
LOGGING_DEFAULT_CONFIG = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '%(asctime)s - %(name)s '
'(%(lineno)d) - %(levelname)s - %(message)s'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'DEBUG',
'formatter': 'simple',
'stream': 'ext://sys.stdout'
}
},
'loggers': {
'mapping_provider': {
'level': 'DEBUG',
'handlers': ['console'],
'propagate': False
}
},
'root': {
'level': 'WARNING',
'handlers': ['console']
}
}
def setup_logging():
"""
Sets up logging using the configured filename.
if LOGGING_CONFIG is defined in the environment, use this for
the filename, otherwise use LOGGING_DEFAULT_CONFIG
"""
logging_config = LOGGING_DEFAULT_CONFIG
if 'LOGGING_CONFIG' in os.environ:
filename = os.environ['LOGGING_CONFIG']
with open(filename) as f:
logging_config = json.load(f)
logging.config.dictConfig(logging_config)
def setup_sentry(sentry_config_params: 'SentryConfig'):
"""
Sets up the sentry instrumentation based on the Configuration.sentry params.
"""
match str(sentry_config_params.level):
case 'debug': level = logging.DEBUG
case 'info': level = logging.INFO
case 'warning': level = logging.WARNING
case 'error': level = logging.ERROR
sentry_sdk.init(
dsn=sentry_config_params.dsn,
environment=sentry_config_params.environment,
integrations=[
LoggingIntegration(
level=logging.INFO, # Capture info and above as breadcrumbs
event_level=level # Send records as events
),
],
)
......@@ -13,7 +13,10 @@ setup(
python_requires=">=3.10",
install_requires=[
"fastapi",
"uvicorn[standard]"
"uvicorn[standard]",
"requests",
"jsonschema",
"sentry_sdk",
],
long_description=open("README.md", encoding="utf-8").read(),
long_description_content_type="text/markdown",
......
import json
import os
import tempfile
from unittest.mock import patch
import pytest
from fastapi.testclient import TestClient
......@@ -5,5 +10,26 @@ from mapping_provider import create_app
@pytest.fixture
def client():
return TestClient(create_app())
def dummy_config():
return {
'sentry': {
'dsn': 'https://token@hostname.geant.org:1111/a/b',
'environment': 'unit tests'
},
'inventory': 'https://dummy-hostname.dummy.domain'
}
@pytest.fixture
def dummy_config_filename(dummy_config):
with tempfile.NamedTemporaryFile(delete=False) as f:
f.write(json.dumps(dummy_config).encode('utf-8'))
f.flush()
yield f.name
@pytest.fixture
def client(dummy_config_filename):
os.environ['SETTINGS_FILENAME'] = dummy_config_filename
with patch('sentry_sdk.init') as _mock_sentry_init:
return TestClient(create_app())
......@@ -9,9 +9,9 @@ commands = ruff check mapping_provider test
[testenv:typecheck]
description = Type-check code with mypy
deps =
mypy
types-jsonschema
types-requests
mypy
types-jsonschema
types-requests
commands = mypy mapping_provider
[testenv:docs]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment