Skip to content
Closed
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
2 changes: 2 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ PACS_DB_PATH=/var/lib/pacs/pacs.db

# General Configuration
LOG_LEVEL=INFO
LOG_DIR=/var/lib/pacs/logs
LOG_TO_FILE=false
1 change: 1 addition & 0 deletions scripts/bash/deploy_arc_ring.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ PACS_PORT=11112
PACS_STORAGE_PATH=${BASE_PATH}/data/storage
PACS_DB_PATH=${BASE_PATH}/data/pacs.db
LOG_LEVEL=INFO
LOG_DIR=${BASE_PATH}/logs
SAMPLE_IMAGES_PATH=${BASE_PATH}/current/sample_images"

# Cross-platform base64 encoding (works on macOS and Linux)
Expand Down
4 changes: 0 additions & 4 deletions scripts/powershell/deploy.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -522,10 +522,6 @@ foreach ($svc in $services) {
Invoke-Nssm -NssmPath $nssmExe -Arguments @("set", $svc.Name, "Description", "Manage Breast Screening Gateway - $($svc.Name)") -Description "set Description"
Invoke-Nssm -NssmPath $nssmExe -Arguments @("set", $svc.Name, "Start", "SERVICE_AUTO_START") -Description "set Start"

$svcLog = Join-Path $logsDir "$($svc.Name).log"
Invoke-Nssm -NssmPath $nssmExe -Arguments @("set", $svc.Name, "AppStdout", "$svcLog") -Description "set AppStdout"
Invoke-Nssm -NssmPath $nssmExe -Arguments @("set", $svc.Name, "AppStderr", "$svcLog") -Description "set AppStderr"

try {
Start-Service -Name $svc.Name
$startedServices += $svc.Name
Expand Down
9 changes: 2 additions & 7 deletions src/modality_emulator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import datetime
import logging
import os
import time

Expand All @@ -18,15 +17,11 @@
from services.dicom import PENDING, PENDING_WARNING, SUCCESS
from services.mwl import MWLStatus
from services.storage import MWLStorage
from telemetry import configure_logging

load_dotenv()

logging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO").upper(),
format=os.getenv("LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"),
)

logger = logging.getLogger(__name__)
logger = configure_logging("Gateway-Emulator")


DICOM_LATERALITIES = ["L", "R"]
Expand Down
20 changes: 11 additions & 9 deletions src/mwl_main.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
"""Entry point for MWL server."""

import logging
import os

from dotenv import load_dotenv

from server import MWLServer
from telemetry import configure_telemetry
from telemetry import configure_logging, configure_telemetry

load_dotenv()

logger = configure_logging("Gateway-MWL")


def main():
"""
Expand All @@ -20,23 +21,24 @@ def main():
MWL_PORT: Port to listen on (default: 4243)
MWL_DB_PATH: Path to the SQLite database file (default: /var/lib/pacs/worklist.db)
"""
logging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO").upper(),
format=os.getenv("LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"),
)

mwl_aet = os.getenv("MWL_AET", "MWL_SCP")
mwl_port = int(os.getenv("MWL_PORT", "4243"))
mwl_db_path = os.getenv("MWL_DB_PATH", "/var/lib/pacs/worklist.db")

mwl_server = MWLServer(mwl_aet, mwl_port, mwl_db_path, block=True)
mwl_server = MWLServer(
ae_title=mwl_aet,
port=mwl_port,
db_path=mwl_db_path,
logger=logger,
block=True,
)

configure_telemetry(service_name="mwl-server")

try:
mwl_server.start()
except KeyboardInterrupt:
logging.info("Received shutdown signal")
logger.info("Received shutdown signal")
mwl_server.stop()


Expand Down
22 changes: 13 additions & 9 deletions src/pacs_main.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
"""Entry point for PACS server."""

import logging
import os

from dotenv import load_dotenv

from server import PACSServer
from telemetry import configure_telemetry
from telemetry import configure_logging, configure_telemetry

load_dotenv()

logger = configure_logging("Gateway-PACS")


def main():
"""
Expand All @@ -21,25 +22,28 @@ def main():
PACS_STORAGE_PATH: Path to store incoming DICOM files (default: /var/lib/pacs/storage)
PACS_DB_PATH: Path to the SQLite database file (default: /var/lib/pacs/pacs.db)
"""
logging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO").upper(),
format=os.getenv("LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"),
)

pacs_aet = os.getenv("PACS_AET", "SCREENING_PACS")
pacs_port = int(os.getenv("PACS_PORT", "4244"))
pacs_storage_path = os.getenv("PACS_STORAGE_PATH", "/var/lib/pacs/storage")
pacs_db_path = os.getenv("PACS_DB_PATH", "/var/lib/pacs/pacs.db")
mwl_db_path = os.getenv("MWL_DB_PATH", "/var/lib/pacs/worklist.db")

pacs_server = PACSServer(pacs_aet, pacs_port, pacs_storage_path, pacs_db_path, block=True, mwl_db_path=mwl_db_path)
pacs_server = PACSServer(
ae_title=pacs_aet,
port=pacs_port,
storage_path=pacs_storage_path,
db_path=pacs_db_path,
logger=logger,
block=True,
mwl_db_path=mwl_db_path,
)

configure_telemetry(service_name="pacs-server")

try:
pacs_server.start()
except KeyboardInterrupt:
logging.info("Received shutdown signal")
logger.info("Received shutdown signal")
pacs_server.stop()


Expand Down
11 changes: 3 additions & 8 deletions src/relay_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import hashlib
import hmac
import json
import logging
import os
import time
import urllib.parse
Expand All @@ -23,11 +22,11 @@
from services.mwl.create_worklist_item import CreateWorklistItem
from services.mwl.update_worklist_item_status import UpdateWorklistItemStatus
from services.storage import MWLStorage
from telemetry import configure_telemetry
from telemetry import configure_logging, configure_telemetry

load_dotenv()

logger = logging.getLogger(__name__)
logger = configure_logging("Gateway-Relay")

DB_PATH = os.getenv("MWL_DB_PATH", "/var/lib/pacs/worklist.db")
AZURE_RELAY_SCOPE = "https://relay.azure.net/.default"
Expand Down Expand Up @@ -174,13 +173,9 @@ def verify_credentials():


async def main():
logging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO").upper(),
format=os.getenv("LOG_FORMAT", "%(asctime)s - %(name)s - %(levelname)s - %(message)s"),
)
configure_telemetry(service_name="relay-listener")

logger.info("Socket Listener Starting...")
logger.info("Gateway Relay Listener Starting...")
verify_credentials()
storage = MWLStorage(db_path=DB_PATH)

Expand Down
22 changes: 13 additions & 9 deletions src/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(
port: int = 4244,
storage_path: str = "/var/lib/pacs/storage",
db_path: str = "/var/lib/pacs/pacs.db",
logger: logging.Logger = logger,
block: bool = True,
mwl_db_path: str = "/var/lib/pacs/worklist.db",
):
Expand All @@ -54,11 +55,12 @@ def __init__(
self.storage = PACSStorage(db_path, storage_path)
self.mwl_storage = MWLStorage(mwl_db_path)
self.ae = None
self.logger = logger
self.block = block

def start(self):
"""Start the PACS server and listen for incoming connections."""
logger.info(f"Starting PACS server: {self.ae_title} on port {self.port}")
self.logger.info(f"Starting PACS server: {self.ae_title} on port {self.port}")

transfer_syntaxes = [
dicom_uid.JPEGLosslessSV1, # Hologic preferred
Expand All @@ -78,16 +80,16 @@ def start(self):
(evt.EVT_C_STORE, CStore(self.storage, mwl_storage=self.mwl_storage).call),
]

logger.info(f"PACS server listening on 0.0.0.0:{self.port}")
logger.info(f"Storage: {self.storage.storage_root}")
logger.info(f"Database: {self.storage.db_path}")
self.logger.info(f"PACS server listening on 0.0.0.0:{self.port}")
self.logger.info(f"Storage: {self.storage.storage_root}")
self.logger.info(f"Database: {self.storage.db_path}")

self.ae.start_server(("0.0.0.0", self.port), block=self.block, evt_handlers=handlers) # type: ignore

def stop(self):
"""Stop the PACS server."""
if self.ae:
logger.info("Stopping PACS server")
self.logger.info("Stopping PACS server")
self.ae.shutdown()
self.storage.close()

Expand All @@ -100,6 +102,7 @@ def __init__(
ae_title: str = "MWL_SCP",
port: int = 4243,
db_path: str = "/var/lib/pacs/worklist.db",
logger: logging.Logger = logger,
block: bool = True,
):
"""
Expand All @@ -115,11 +118,12 @@ def __init__(
self.port = port
self.storage = MWLStorage(db_path)
self.ae = None
self.logger = logger
self.block = block

def start(self):
"""Start the MWL server."""
logger.info(f"Starting MWL server: {self.ae_title} on port {self.port}")
self.logger.info(f"Starting MWL server: {self.ae_title} on port {self.port}")

self.ae = AE(ae_title=self.ae_title)
self.ae.add_supported_context(Verification)
Expand All @@ -133,13 +137,13 @@ def start(self):
(evt.EVT_N_SET, NSet(self.storage).call),
]

logger.info(f"MWL server listening on 0.0.0.0:{self.port}")
logger.info(f"Database: {self.storage.db_path}")
self.logger.info(f"MWL server listening on 0.0.0.0:{self.port}")
self.logger.info(f"Database: {self.storage.db_path}")

self.ae.start_server(("0.0.0.0", self.port), block=self.block, evt_handlers=handlers) # type: ignore

def stop(self):
"""Stop the MWL server."""
if self.ae:
logger.info("Stopping MWL server")
self.logger.info("Stopping MWL server")
self.ae.shutdown()
61 changes: 59 additions & 2 deletions src/telemetry.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,74 @@
import logging
import os
from pathlib import Path

from dotenv import load_dotenv

logger = logging.getLogger(__name__)

DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
DEFAULT_MAX_LOG_FILE_SIZE = 10 * 1024 * 1024 # 10 MB

# Suppress verbose logging from OpenTelemetry and Azure Monitor libraries
for package_name in [
SUPPRESSED_LOG_PACKAGES = [
"azure.monitor.opentelemetry",
"azure.core.pipeline.policies.http_logging_policy",
]:
]

load_dotenv()

LOG_DIR = os.getenv("LOG_DIR", "/var/lib/pacs/logs")
os.makedirs(LOG_DIR, exist_ok=True)

for package_name in SUPPRESSED_LOG_PACKAGES:
package_logger = logging.getLogger(package_name)
package_logger.setLevel(logging.WARNING)


def configure_logging(
name: str, max_log_size: int = DEFAULT_MAX_LOG_FILE_SIZE, backup_count: int = 5
) -> logging.Logger:
"""Configure logging for the application.

Args:
name: Optional file to write logs to. If None, logs will only go to the console.
max_log_size: Maximum size of the log file before rotation occurs (default: 10 MB).
backup_count: Number of backup log files to keep (default: 5).
"""
level = os.getenv("LOG_LEVEL", "INFO").upper()
format = os.getenv("LOG_FORMAT", DEFAULT_LOG_FORMAT)
logging.basicConfig(format=format, level=level)
configured_logger = logging.getLogger(name)
configured_logger.setLevel(level)
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter(format))
configured_logger.addHandler(console_handler)

if os.getenv("LOG_TO_FILE", "true").lower() == "true":
configured_logger.addHandler(log_rotation_handler(name, max_bytes=max_log_size, backup_count=backup_count))

return configured_logger


def log_rotation_handler(name: str, max_bytes, backup_count) -> logging.Handler:
"""Create a rotating log handler.

Args:
name: The name of the logger, used to derive the file name to write logs to.
max_bytes: The maximum size of the log file before rotation occurs.
backup_count: The number of backup log files to keep.

Returns:
A configured logging.Handler instance.
"""
from logging.handlers import RotatingFileHandler

log_file_path = Path(LOG_DIR) / f"{name}.log"
handler = RotatingFileHandler(log_file_path, maxBytes=max_bytes, backupCount=backup_count)
handler.setFormatter(logging.Formatter(DEFAULT_LOG_FORMAT))
return handler


def configure_telemetry(service_name: str | None = None) -> None:
"""Configure OpenTelemetry with Azure Monitor.

Expand Down
Loading
Loading