Skip to content

Commit

Permalink
Add docstring for public api
Browse files Browse the repository at this point in the history
  • Loading branch information
draincoder committed Mar 5, 2024
1 parent 3d5d2b1 commit 9bc948d
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 0 deletions.
35 changes: 35 additions & 0 deletions src/asgi_monitor/integrations/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,33 @@

@dataclass
class TracingConfig(CommonTracingConfig):
"""Configuration class for the OpenTelemetry middleware.
Consult the OpenTelemetry ASGI documentation for more info about the configuration options.
https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asgi/asgi.html
"""

exclude_urls_env_key: str = "FASTAPI"
"""Key to use when checking whether a list of excluded urls is passed via ENV.
OpenTelemetry supports excluding urls by passing an env in the format '{exclude_urls_env_key}_EXCLUDED_URLS'.
"""

scope_span_details_extractor: Callable[[Any], tuple[str, dict[str, Any]]] = _get_default_span_details
"""
Callback which should return a string and a tuple, representing the desired default span name and a dictionary
with any additional span attributes to set.
"""


def setup_tracing(app: FastAPI, config: TracingConfig) -> None:
"""
Set up tracing for a FastAPI application.
The function adds a TracingMiddleware to the FastAPI application based on TracingConfig.
:param FastAPI app: The FastAPI application instance.
:param TracingConfig config: The Open Telemetry config.
:returns: None
"""

app.add_middleware(TracingMiddleware, config=config)


Expand All @@ -43,6 +65,19 @@ def setup_metrics(
include_trace_exemplar: bool,
include_metrics_endpoint: bool,
) -> None:
"""
Set up metrics for a FastAPI application.
This function adds a MetricsMiddleware to the FastAPI application with the specified parameters.
If include_metrics_endpoint is True, it also adds a route for "/metrics" that returns Prometheus default metrics.
:param FastAPI app: The FastAPI application instance.
:param str app_name: The name of the FastAPI application.
:param str metrics_prefix: The prefix to use for the metrics (default is "fastapi").
:param bool include_trace_exemplar: Whether to include trace exemplars in the metrics.
:param bool include_metrics_endpoint: Whether to include a /metrics endpoint.
:returns: None
"""

app.add_middleware(
MetricsMiddleware,
app_name=app_name,
Expand Down
35 changes: 35 additions & 0 deletions src/asgi_monitor/integrations/starlette.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,21 @@ def _get_path(request: Request) -> tuple[str, bool]:

@dataclass
class TracingConfig(CommonTracingConfig):
"""Configuration class for the OpenTelemetry middleware.
Consult the OpenTelemetry ASGI documentation for more info about the configuration options.
https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asgi/asgi.html
"""

exclude_urls_env_key: str = "STARLETTE"
"""Key to use when checking whether a list of excluded urls is passed via ENV.
OpenTelemetry supports excluding urls by passing an env in the format '{exclude_urls_env_key}_EXCLUDED_URLS'.
"""

scope_span_details_extractor: Callable[[Any], tuple[str, dict[str, Any]]] = _get_default_span_details
"""
Callback which should return a string and a tuple, representing the desired default span name and a dictionary
with any additional span attributes to set.
"""


class TracingMiddleware:
Expand Down Expand Up @@ -162,6 +175,15 @@ async def get_metrics(request: Request) -> Response:


def setup_tracing(app: Starlette, config: TracingConfig) -> None:
"""
Set up tracing for a Starlette application.
The function adds a TracingMiddleware to the Starlette application based on TracingConfig.
:param Starlette app: The FastAPI application instance.
:param TracingConfig config: The Open Telemetry config.
:returns: None
"""

app.add_middleware(TracingMiddleware, config=config)


Expand All @@ -173,6 +195,19 @@ def setup_metrics(
include_trace_exemplar: bool,
include_metrics_endpoint: bool,
) -> None:
"""
Set up metrics for a Starlette application.
This function adds a MetricsMiddleware to the Starlette application with the specified parameters.
If include_metrics_endpoint is True, it also adds a route for "/metrics" that returns Prometheus default metrics.
:param Starlette app: The Starlette application instance.
:param str app_name: The name of the Starlette application.
:param str metrics_prefix: The prefix to use for the metrics (default is "starlette").
:param bool include_trace_exemplar: Whether to include trace exemplars in the metrics.
:param bool include_metrics_endpoint: Whether to include a /metrics endpoint.
:returns: None
"""

app.add_middleware(
MetricsMiddleware,
app_name=app_name,
Expand Down
9 changes: 9 additions & 0 deletions src/asgi_monitor/logging/configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ def configure_logging(
json_format: bool,
include_trace: bool,
) -> None:
"""
Default logging setting for logging and structlog.
:param str | int level: Logging level.
:param bool json_format: The format of the logs. If True, the log will be rendered as JSON.
:param bool include_trace: Include tracing information ("trace_id", "span_id", "parent_span_id", "service.name").
:returns: None
"""

_configure_structlog(json_format=json_format, include_trace=include_trace)
_configure_default_logging(level=level, json_format=json_format, include_trace=include_trace)

Expand Down
4 changes: 4 additions & 0 deletions src/asgi_monitor/logging/gunicorn/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@


class StubbedGunicornLogger(Logger):
"""
Custom Gunicorn logger that allows setting up log levels based on Gunicorn configuration.
"""

def setup(self, cfg: Config) -> None:
_name_to_level = {
"CRITICAL": logging.CRITICAL,
Expand Down
4 changes: 4 additions & 0 deletions src/asgi_monitor/logging/gunicorn/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@


class GunicornStandaloneApplication(BaseApplication):
"""
Custom standalone application class for running a Gunicorn server with a ASGI application using Uvicorn worker.
"""

def __init__(
self,
app: Any,
Expand Down
9 changes: 9 additions & 0 deletions src/asgi_monitor/logging/uvicorn/log_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,15 @@ def build_uvicorn_log_config(
json_format: bool,
include_trace: bool,
) -> dict[str, Any]:
"""
Building a Uvicorn log config.
:param str | int level: Logging level.
:param bool json_format: The format of the logs. If True, the log will be rendered as JSON.
:param bool include_trace: Include tracing information ("trace_id", "span_id", "parent_span_id", "service.name").
:returns: Logging configuration for Uvicorn
"""

level_name = logging.getLevelName(level)

if json_format:
Expand Down
28 changes: 28 additions & 0 deletions src/asgi_monitor/logging/uvicorn/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,52 @@ def __init__(self, *args: Any, **kwargs: Any) -> None:


class StructlogTextLogUvicornWorker(StructlogDefaultUvicornWorker):
"""
UvicornWorker with custom logging configuration:
- level: DEBUG
- json_format: False
- include_trace: False
"""

level: int = logging.DEBUG
json_format: bool = False
include_trace: bool = False


class StructlogTraceTextLogUvicornWorker(StructlogDefaultUvicornWorker):
"""
UvicornWorker with custom logging configuration:
- level: DEBUG
- json_format: False
- include_trace: False
"""

level: int = logging.DEBUG
json_format: bool = False
include_trace: bool = True


class StructlogJSONLogUvicornWorker(StructlogDefaultUvicornWorker):
"""
UvicornWorker with custom logging configuration:
- level: DEBUG
- json_format: True
- include_trace: False
"""

level: int = logging.DEBUG
json_format: bool = True
include_trace: bool = False


class StructlogTraceJSONLogUvicornWorker(StructlogDefaultUvicornWorker):
"""
UvicornWorker with custom logging configuration:
- level: DEBUG
- json_format: True
- include_trace: True
"""

level: int = logging.DEBUG
json_format: bool = True
include_trace: bool = True
11 changes: 11 additions & 0 deletions src/asgi_monitor/metrics/get_latest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,23 @@

@dataclass(frozen=True, slots=True)
class MetricsResponse:
"""
Represents a response containing metrics data.
"""

status_code: int
headers: dict[str, str]
payload: bytes


def get_latest_metrics(*, openmetrics_format: bool) -> MetricsResponse:
"""
Generates the latest metrics data in either Prometheus or OpenMetrics format.
:param bool openmetrics_format: A flag indicating whether to generate metrics in OpenMetrics format.
:returns: MetricsResponse
"""

registry = REGISTRY

if "PROMETHEUS_MULTIPROC_DIR" in os.environ:
Expand Down
29 changes: 29 additions & 0 deletions src/asgi_monitor/tracing/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,40 @@

@dataclass
class CommonTracingConfig:
"""Configuration class for the OpenTelemetry middleware.
Consult the OpenTelemetry ASGI documentation for more info about the configuration options.
https://opentelemetry-python-contrib.readthedocs.io/en/latest/instrumentation/asgi/asgi.html
"""

exclude_urls_env_key: str
"""Key to use when checking whether a list of excluded urls is passed via ENV.
OpenTelemetry supports excluding urls by passing an env in the format '{exclude_urls_env_key}_EXCLUDED_URLS'.
"""

scope_span_details_extractor: Callable[[Any], tuple[str, dict[str, Any]]]
"""
Callback which should return a string and a tuple, representing the desired default span name and a dictionary
with any additional span attributes to set.
"""

server_request_hook_handler: OpenTelemetryHookHandler | None = field(default=None)
"""Optional callback which is called with the server span and ASGI scope object for every incoming request."""

client_request_hook_handler: OpenTelemetryHookHandler | None = field(default=None)
"""Optional callback which is called with the internal span and an ASGI scope which is sent as a dictionary for when
the method receive is called.
"""

client_response_hook_handler: OpenTelemetryHookHandler | None = field(default=None)
"""Optional callback which is called with the internal span and an ASGI event which is sent as a dictionary for when
the method send is called.
"""

meter_provider: MeterProvider | None = field(default=None)
"""Optional meter provider to use."""

tracer_provider: TracerProvider | None = field(default=None)
"""Optional tracer provider to use."""

meter: Meter | None = field(default=None)
"""Optional meter to use."""

3 comments on commit 9bc948d

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report •
FileStmtsMissCoverMissing
src/asgi_monitor/integrations
   fastapi.py180100% 
   starlette.py100199%123
src/asgi_monitor/logging
   configure.py250100% 
src/asgi_monitor/logging/gunicorn
   logger.py10460%15, 26–28
   worker.py140100% 
src/asgi_monitor/logging/uvicorn
   log_config.py470100% 
   worker.py28292%22, 27
src/asgi_monitor/metrics
   get_latest.py210100% 
src/asgi_monitor/tracing
   config.py240100% 
TOTAL390798% 

3.10

Tests Skipped Failures Errors Time
37 0 💤 0 ❌ 0 🔥 4.572s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report •
FileStmtsMissCoverMissing
src/asgi_monitor/integrations
   fastapi.py180100% 
   starlette.py100199%123
src/asgi_monitor/logging
   configure.py250100% 
src/asgi_monitor/logging/gunicorn
   logger.py10460%15, 26–28
   worker.py140100% 
src/asgi_monitor/logging/uvicorn
   log_config.py470100% 
   worker.py28292%22, 27
src/asgi_monitor/metrics
   get_latest.py210100% 
src/asgi_monitor/tracing
   config.py240100% 
TOTAL390798% 

3.11

Tests Skipped Failures Errors Time
37 0 💤 0 ❌ 0 🔥 4.607s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report •
FileStmtsMissCoverMissing
src/asgi_monitor/integrations
   fastapi.py180100% 
   starlette.py100199%123
src/asgi_monitor/logging
   configure.py250100% 
src/asgi_monitor/logging/gunicorn
   logger.py10460%15, 26–28
   worker.py140100% 
src/asgi_monitor/logging/uvicorn
   log_config.py470100% 
   worker.py28292%22, 27
src/asgi_monitor/metrics
   get_latest.py210100% 
src/asgi_monitor/tracing
   config.py240100% 
TOTAL390798% 

3.12

Tests Skipped Failures Errors Time
37 0 💤 0 ❌ 0 🔥 6.831s ⏱️

Please sign in to comment.