diff --git a/medcat-service/README.md b/medcat-service/README.md index 56bfdf74..5a6073fb 100644 --- a/medcat-service/README.md +++ b/medcat-service/README.md @@ -328,6 +328,7 @@ The following environment variables are available for tailoring the MedCAT Servi - `APP_MODEL_META_PATH_LIST` - the list of paths to meta-annotation models, each separated by `:` character (optional), - `APP_BULK_NPROC` - the number of threads used in bulk processing (default: `8`), - `APP_MEDCAT_MODEL_PACK` - MedCAT Model Pack path, if this parameter has a value IT WILL BE LOADED FIRST OVER EVERYTHING ELSE (CDB, Vocab, MetaCATs, etc.) declared above. +- `APP_ENABLE_METRICS` - Enable prometheus metrics collection served on the path /metrics ### Shared Memory (`DOCKER_SHM_SIZE`) diff --git a/medcat-service/medcat_service/config.py b/medcat-service/medcat_service/config.py index 2a2fa722..4f2c5eb8 100644 --- a/medcat-service/medcat_service/config.py +++ b/medcat-service/medcat_service/config.py @@ -1,5 +1,5 @@ import logging -from typing import Any, Optional, Tuple, Union +from typing import Any import torch from pydantic import AliasChoices, Field, field_validator @@ -19,13 +19,19 @@ def _coerce_loglevel(v: Any) -> int: return logging.INFO -class Settings(BaseSettings): +class ObservabilitySettings(BaseSettings): + model_config = SettingsConfigDict(frozen=True, env_prefix="APP_") + + enable_metrics: bool = Field( + default=False, description="Enable prometheus metrics collection served on the path /metrics") + +class Settings(BaseSettings): model_config = SettingsConfigDict( frozen=True, env_prefix="", # no prefix; we specify full env names via alias case_sensitive=False, - populate_by_name=True + populate_by_name=True, ) app_root_path: str = Field( @@ -34,23 +40,22 @@ class Settings(BaseSettings): examples=["/medcat-service"], ) - deid_mode: bool = Field(default=False, - validation_alias=AliasChoices("deid_mode", "MEDCAT_DEID_MODE"), - description="Enable DEID mode" - ) + deid_mode: bool = Field( + default=False, validation_alias=AliasChoices("deid_mode", "MEDCAT_DEID_MODE"), description="Enable DEID mode" + ) deid_redact: bool = Field( default=True, validation_alias=AliasChoices("deid_redact", "MEDCAT_DEID_REDACT"), - description="Enable DEID redaction. Returns text like [***] instead of [ANNOTATION]" + description="Enable DEID redaction. Returns text like [***] instead of [ANNOTATION]", ) # Model paths - model_cdb_path: Optional[str] = Field("/cat/models/medmen/cdb.dat", alias="APP_MODEL_CDB_PATH") - model_vocab_path: Optional[str] = Field("/cat/models/medmen/vocab.dat", alias="APP_MODEL_VOCAB_PATH") - model_meta_path_list: Union[str, Tuple[str, ...]] = Field(default=(), alias="APP_MODEL_META_PATH_LIST") - model_rel_path_list: Union[str, Tuple[str, ...]] = Field(default=(), alias="APP_MODEL_REL_PATH_LIST") - medcat_model_pack: Optional[str] = Field("", alias="APP_MEDCAT_MODEL_PACK") - model_cui_filter_path: Optional[str] = Field("", alias="APP_MODEL_CUI_FILTER_PATH") + model_cdb_path: str | None = Field("/cat/models/medmen/cdb.dat", alias="APP_MODEL_CDB_PATH") + model_vocab_path: str | None = Field("/cat/models/medmen/vocab.dat", alias="APP_MODEL_VOCAB_PATH") + model_meta_path_list: str | tuple[str, ...] = Field(default=(), alias="APP_MODEL_META_PATH_LIST") + model_rel_path_list: str | tuple[str, ...] = Field(default=(), alias="APP_MODEL_REL_PATH_LIST") + medcat_model_pack: str | None = Field("", alias="APP_MEDCAT_MODEL_PACK") + model_cui_filter_path: str | None = Field("", alias="APP_MODEL_CUI_FILTER_PATH") spacy_model: str = Field("", alias="MEDCAT_SPACY_MODEL") # ---- App logging & MedCAT logging ---- @@ -70,6 +75,8 @@ class Settings(BaseSettings): # e.g. "dict" | "list" | "json" (service currently uses "dict" default) annotations_entity_output_mode: str = Field(default="dict", alias="MEDCAT_ANNOTATIONS_ENTITY_OUTPUT_MODE") + observability: ObservabilitySettings = ObservabilitySettings() + # ---- Normalizers --------------------------------------------------------- @field_validator("app_log_level", "medcat_log_level", mode="before") @classmethod @@ -99,7 +106,7 @@ def env_name(cls, field: str) -> str: @field_validator("bulk_nproc", mode="before") def adjust_bulk_nproc(cls, num_procs: int) -> int: - """ This method is used to adjust the number of processes to use for bulk processing. + """This method is used to adjust the number of processes to use for bulk processing. Set number of processes to 1 if MPS (Apple Sillicon) is available, as MPS does not support multiprocessing. Args: diff --git a/medcat-service/medcat_service/main.py b/medcat-service/medcat_service/main.py index f92aae11..d141ce2c 100644 --- a/medcat-service/medcat_service/main.py +++ b/medcat-service/medcat_service/main.py @@ -36,6 +36,11 @@ gr.mount_gradio_app(app, io, path="/demo") +if settings.observability.enable_metrics: + from prometheus_fastapi_instrumentator import Instrumentator + + Instrumentator(excluded_handlers=["/api/health.*", "/metrics"],).instrument(app).expose(app, tags=["admin"]) + @app.exception_handler(HealthCheckFailedException) async def healthcheck_failed_exception_handler(request: Request, exc: HealthCheckFailedException): @@ -45,6 +50,7 @@ async def healthcheck_failed_exception_handler(request: Request, exc: HealthChec if __name__ == "__main__": # Only run this when directly executing `python main.py` for local dev. import os + import uvicorn uvicorn.run("medcat_service.main:app", host="0.0.0.0", port=int(os.environ.get("SERVER_PORT", 8000)))