Skip to content

Commit

Permalink
Enhancement: Add logging on api endpoints (#296)
Browse files Browse the repository at this point in the history
* add logging on api endpoints

* use central log config

* add examplfe for a log filter

* add example to mask sensitive data with regex

* add more log filter examples

* use same log config throughout the whole package
CM000n authored Jan 13, 2024
1 parent 77d1de0 commit f36b119
Showing 12 changed files with 119 additions and 16 deletions.
40 changes: 32 additions & 8 deletions mytoyota/api.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Toyota Connected Services API."""

import logging
from datetime import date, datetime, timezone
from uuid import uuid4

import mytoyota.utils.logging.logging_config # noqa # pylint: disable=unused-import
from mytoyota.const import (
VEHICLE_ASSOCIATION_ENDPOINT,
VEHICLE_GLOBAL_REMOTE_ELECTRIC_STATUS_ENDPOINT,
@@ -24,6 +26,8 @@
from mytoyota.models.endpoints.vehicle_guid import VehiclesResponseModel
from mytoyota.models.endpoints.vehicle_health import VehicleHealthResponseModel

_LOGGER: logging.Logger = logging.getLogger(__name__)


class Api:
"""API Class. Allows access to available endpoints to retrieve the raw data."""
@@ -72,7 +76,11 @@ async def set_vehicle_alias_endpoint(self, alias: str, guid: str, vin: str):

async def get_vehicles_endpoint(self) -> VehiclesResponseModel:
"""Return list of vehicles registered with provider."""
return await self._request_and_parse(VehiclesResponseModel, "GET", VEHICLE_GUID_ENDPOINT)
parsed_response = await self._request_and_parse(
VehiclesResponseModel, "GET", VEHICLE_GUID_ENDPOINT
)
_LOGGER.debug(msg=f"Parsed 'VehiclesResponseModel': {parsed_response}")
return parsed_response

async def get_location_endpoint(self, vin: str) -> LocationResponseModel:
"""Get the last known location of your car. Only updates when car is parked.
@@ -87,9 +95,11 @@ async def get_location_endpoint(self, vin: str) -> LocationResponseModel:
-------
LocationResponseModel: A pydantic model for the location response
"""
return await self._request_and_parse(
parsed_response = await self._request_and_parse(
LocationResponseModel, "GET", VEHICLE_LOCATION_ENDPOINT, vin=vin
)
_LOGGER.debug(msg=f"Parsed 'LocationResponseModel': {parsed_response}")
return parsed_response

async def get_vehicle_health_status_endpoint(self, vin: str) -> VehicleHealthResponseModel:
r"""Get the latest health status.
@@ -105,18 +115,22 @@ async def get_vehicle_health_status_endpoint(self, vin: str) -> VehicleHealthRes
-------
VehicleHealthResponseModel: A pydantic model for the vehicle health response
"""
return await self._request_and_parse(
parsed_response = await self._request_and_parse(
VehicleHealthResponseModel, "GET", VEHICLE_HEALTH_STATUS_ENDPOINT, vin=vin
)
_LOGGER.debug(msg=f"Parsed 'VehicleHealthResponseModel': {parsed_response}")
return parsed_response

async def get_remote_status_endpoint(self, vin: str) -> RemoteStatusResponseModel:
"""Get information about the vehicle."""
return await self._request_and_parse(
parsed_response = await self._request_and_parse(
RemoteStatusResponseModel,
"GET",
VEHICLE_GLOBAL_REMOTE_STATUS_ENDPOINT,
vin=vin,
)
_LOGGER.debug(msg=f"Parsed 'RemoteStatusResponseModel': {parsed_response}")
return parsed_response

async def get_vehicle_electric_status_endpoint(self, vin: str) -> ElectricResponseModel:
r"""Get the latest electric status.
@@ -132,12 +146,14 @@ async def get_vehicle_electric_status_endpoint(self, vin: str) -> ElectricRespon
-------
ElectricResponseModel: A pydantic model for the electric response
"""
return await self._request_and_parse(
parsed_response = await self._request_and_parse(
ElectricResponseModel,
"GET",
VEHICLE_GLOBAL_REMOTE_ELECTRIC_STATUS_ENDPOINT,
vin=vin,
)
_LOGGER.debug(msg=f"Parsed 'ElectricResponseModel': {parsed_response}")
return parsed_response

async def get_telemetry_endpoint(self, vin: str) -> TelemetryResponseModel:
"""Get the latest telemetry status.
@@ -152,9 +168,11 @@ async def get_telemetry_endpoint(self, vin: str) -> TelemetryResponseModel:
-------
TelemetryResponseModel: A pydantic model for the telemetry response
"""
return await self._request_and_parse(
parsed_response = await self._request_and_parse(
TelemetryResponseModel, "GET", VEHICLE_TELEMETRY_ENDPOINT, vin=vin
)
_LOGGER.debug(msg=f"Parsed 'TelemetryResponseModel': {parsed_response}")
return parsed_response

async def get_notification_endpoint(self, vin: str) -> NotificationResponseModel:
"""Get all available notifications for the vehicle.
@@ -171,12 +189,14 @@ async def get_notification_endpoint(self, vin: str) -> NotificationResponseModel
-------
NotificationResponseModel: A pydantic model for the notification response
"""
return await self._request_and_parse(
parsed_response = await self._request_and_parse(
NotificationResponseModel,
"GET",
VEHICLE_NOTIFICATION_HISTORY_ENDPOINT,
vin=vin,
)
_LOGGER.debug(msg=f"Parsed 'NotificationResponseModel': {parsed_response}")
return parsed_response

async def get_trips_endpoint( # noqa: PLR0913
self,
@@ -217,4 +237,8 @@ async def get_trips_endpoint( # noqa: PLR0913
limit=limit,
offset=offset,
)
return await self._request_and_parse(TripsResponseModel, "GET", endpoint, vin=vin)
parsed_response = await self._request_and_parse(
TripsResponseModel, "GET", endpoint, vin=vin
)
_LOGGER.debug(msg=f"Parsed 'TripsResponseModel': {parsed_response}")
return parsed_response
3 changes: 2 additions & 1 deletion mytoyota/client.py
Original file line number Diff line number Diff line change
@@ -11,13 +11,14 @@
import logging
from typing import List, Optional

import mytoyota.utils.logging.logging_config # noqa # pylint: disable=unused-import
from mytoyota.api import Api
from mytoyota.models.vehicle import Vehicle

from .controller import Controller
from .exceptions import ToyotaInvalidUsernameError

_LOGGER: logging.Logger = logging.getLogger(__package__)
_LOGGER: logging.Logger = logging.getLogger(__name__)


class MyT:
5 changes: 3 additions & 2 deletions mytoyota/controller.py
Original file line number Diff line number Diff line change
@@ -11,16 +11,17 @@
import httpx
import jwt

import mytoyota.utils.logging.logging_config # noqa # pylint: disable=unused-import
from mytoyota.const import (
ACCESS_TOKEN_URL,
API_BASE_URL,
AUTHENTICATE_URL,
AUTHORIZE_URL,
)
from mytoyota.exceptions import ToyotaApiError, ToyotaInternalError, ToyotaLoginError
from mytoyota.utils.logs import format_httpx_response
from mytoyota.utils.logging.log_utils import format_httpx_response

_LOGGER: logging.Logger = logging.getLogger(__package__)
_LOGGER: logging.Logger = logging.getLogger(__name__)

CACHE_FILENAME: Path = Path.home() / ".cache" / "toyota_credentials_cache_contains_secrets"

5 changes: 3 additions & 2 deletions mytoyota/models/vehicle.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@

from arrow import Arrow

import mytoyota.utils.logging.logging_config # noqa # pylint: disable=unused-import
from mytoyota.api import Api
from mytoyota.models.dashboard import Dashboard
from mytoyota.models.endpoints.vehicle_guid import VehicleGuidModel
@@ -20,9 +21,9 @@
from mytoyota.models.summary import Summary, SummaryType
from mytoyota.models.trips import Trip
from mytoyota.utils.helpers import add_with_none
from mytoyota.utils.logs import censor_all
from mytoyota.utils.logging.log_utils import censor_all

_LOGGER: logging.Logger = logging.getLogger(__package__)
_LOGGER: logging.Logger = logging.getLogger(__name__)


class Vehicle:
3 changes: 2 additions & 1 deletion mytoyota/statistics.py
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import arrow
from arrow import Arrow

import mytoyota.utils.logging.logging_config # noqa # pylint: disable=unused-import
from mytoyota.const import (
BUCKET,
DATA,
@@ -29,7 +30,7 @@
convert_to_mpg,
)

_LOGGER: logging.Logger = logging.getLogger(__package__)
_LOGGER: logging.Logger = logging.getLogger(__name__)


class Statistics:
4 changes: 3 additions & 1 deletion mytoyota/utils/conversions.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"""Conversion utilities used."""
import logging

_LOGGER: logging.Logger = logging.getLogger(__package__)
import mytoyota.utils.logging.logging_config # noqa # pylint: disable=unused-import

_LOGGER: logging.Logger = logging.getLogger(__name__)


def convert_to_miles(kilometers: float) -> float:
33 changes: 33 additions & 0 deletions mytoyota/utils/logging/log_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
version: 1
disable_existing_loggers: False

formatters:
simple:
format: "%(asctime)s %(levelname)s %(message)s"
datefmt: "%Y-%m-%d %H:%M:%S"

handlers:
file:
class: "logging.FileHandler"
formatter: "simple"
filename: ".log"
mode: "w"
filters:
- "logfilter"

filters:
logfilter:
(): mytoyota.utils.logging.log_filters.RedactingFilter
patterns:
- 'access_token\":\s*\"([^\"]*)'
- 'id_token\":\s*\"([^\"]*)'
- 'subscriberGuid\":\s*\"([^\"]*)'
- 'contractId\":\s*\"([^\"]*)'
- 'vin\":\s*\"([^\"]*)'
- 'euiccid\":\s*\"([^\"]*)'
- "guid':\\s*'([^']*)"

root:
level: "DEBUG"
handlers:
- "file"
24 changes: 24 additions & 0 deletions mytoyota/utils/logging/log_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Module with filter log filter classes."""
import logging
import re


class RedactingFilter(logging.Filter):
"""Helper class to filter logs by given pattern via 'logging.Filter'."""

def __init__(self, patterns):
"""Initialise RedactingFilter class."""
super(RedactingFilter, self).__init__()
self._patterns = patterns

def filter(self, record):
"""Modify the log record to mask sensitive data."""
record.msg = self.mask_sensitive_data(record.msg)
return True

def mask_sensitive_data(self, msg):
"""Mask sensitive data in logs by given patterns."""
for pattern in self._patterns:
compiled_pattern = re.compile(pattern)
msg = compiled_pattern.sub("****", msg)
return msg
File renamed without changes.
13 changes: 13 additions & 0 deletions mytoyota/utils/logging/logging_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Load a central config for logging."""
import logging
import logging.config
from pathlib import Path

import yaml

BASE_DIR = Path(__file__).parent

with open(file=BASE_DIR / "log_config.yaml", mode="r", encoding="utf-8") as config:
config_dict = yaml.safe_load(config)

logging.config.dictConfig(config_dict)
3 changes: 3 additions & 0 deletions simple_client_example.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""Simple test of new API Changes."""
import asyncio
import json
import logging
import pprint
from datetime import date, timedelta

import mytoyota.utils.logging.logging_config # noqa # pylint: disable=unused-import
from mytoyota.client import MyT
from mytoyota.models.summary import SummaryType

pp = pprint.PrettyPrinter(indent=4)
logger = logging.getLogger(__name__)

# Set your username and password in a file on top level called "credentials.json" in the format:
# {
2 changes: 1 addition & 1 deletion tests/test_utils/test_logs.py
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
import pytest
from httpx import Request, Response

from mytoyota.utils.logs import (
from mytoyota.utils.logging.log_utils import (
censor_all,
censor_string,
censor_value,

0 comments on commit f36b119

Please sign in to comment.