Skip to content
8 changes: 2 additions & 6 deletions docs/snippets/static14.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@
from fastcs.controllers import Controller
from fastcs.datatypes import Enum, Float, Int, String
from fastcs.launch import FastCS
from fastcs.logging import bind_logger, configure_logging
from fastcs.logging import configure_logging, logger
from fastcs.methods import command, scan
from fastcs.transports.epics import EpicsGUIOptions, EpicsIOCOptions
from fastcs.transports.epics.ca import EpicsCATransport

logger = bind_logger(__name__)

NumberT = TypeVar("NumberT", int, float)


Expand All @@ -33,8 +31,6 @@ class TemperatureControllerAttributeIO(
def __init__(self, connection: IPConnection, suffix: str = ""):
super().__init__()

self.logger = bind_logger(__class__.__name__)

self._connection = connection
self._suffix = suffix

Expand All @@ -49,7 +45,7 @@ async def send(
) -> None:
command = f"{attr.io_ref.name}{self._suffix}={attr.dtype(value)}"

self.logger.info("Sending attribute value", command=command, attribute=attr)
logger.info("Sending attribute value", command=command, attribute=attr)

await self._connection.send_command(f"{command}\r\n")

Expand Down
8 changes: 2 additions & 6 deletions docs/snippets/static15.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,11 @@
from fastcs.controllers import Controller
from fastcs.datatypes import Enum, Float, Int, String
from fastcs.launch import FastCS
from fastcs.logging import LogLevel, bind_logger, configure_logging
from fastcs.logging import LogLevel, configure_logging, logger
from fastcs.methods import command, scan
from fastcs.transports.epics import EpicsGUIOptions, EpicsIOCOptions
from fastcs.transports.epics.ca import EpicsCATransport

logger = bind_logger(__name__)

NumberT = TypeVar("NumberT", int, float)


Expand All @@ -33,8 +31,6 @@ class TemperatureControllerAttributeIO(
def __init__(self, connection: IPConnection, suffix: str = ""):
super().__init__()

self.logger = bind_logger(__class__.__name__)

self._connection = connection
self._suffix = suffix

Expand All @@ -52,7 +48,7 @@ async def send(
) -> None:
command = f"{attr.io_ref.name}{self._suffix}={attr.dtype(value)}"

self.logger.info("Sending attribute value", command=command, attribute=attr)
logger.info("Sending attribute value", command=command, attribute=attr)

await self._connection.send_command(f"{command}\r\n")

Expand Down
19 changes: 9 additions & 10 deletions docs/tutorials/static-drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,10 +409,8 @@ DEMO:R1:Enabled_RBV Off

FastCS has convenient logging support to provide status and metrics from the
application. To enable logging from the core framework call `configure_logging` with no
arguments (the default logging level is INFO). To log messages from a driver, either
import the singleton `logger` directly, or to provide more context to the message, call
`bind_logger` with a name (usually either the name of the module or the name of the
class).
arguments (the default logging level is INFO). To log messages from a driver, import the
singleton `logger` directly.

Create a module-level logger to log status of the application start up. Create a class
logger for `TemperatureControllerAttributeIO` to log the commands it sends.
Expand All @@ -421,7 +419,7 @@ logger for `TemperatureControllerAttributeIO` to log the commands it sends.
:class: dropdown, hint

:::{literalinclude} /snippets/static14.py
:emphasize-lines: 15,20-21,53-55,116,123
:emphasize-lines: 13,48,110,117
:::

::::
Expand All @@ -434,7 +432,7 @@ Try setting a PV and check the console for the log message it prints.

A similar log message could be added for the update method of the IO, but this would be
very verbose. For this use case FastCS provides the `Tracer` class, which is inherited
by `AttributeIO`, among other core FastCS classes. This adds a enables logging `TRACE`
by `AttributeIO`, among other core FastCS classes. This enables the logging of `TRACE`
level log messages that are disabled by default, but can be enabled at runtime.

Update the `send` method of the IO to log a message showing the query that was sent and
Expand All @@ -446,13 +444,14 @@ visible.
:class: dropdown, hint

:::{literalinclude} /snippets/static15.py
:emphasize-lines: 15,47-49,119
:emphasize-lines: 13,49-51,120
:::

::::

Enable tracing on the `power` attribute by calling `enable_tracing` and then enable a
ramp so that the value updates. Check the console to see the messages. Call `disable_tracing` to disable the log messages for `power.
ramp so that the value updates. Check the console to see the messages. Call
`disable_tracing` to disable the log messages for `power`.

```
In [1]: controller.power.enable_tracing()
Expand All @@ -472,7 +471,7 @@ In [1]: controller.power.enable_tracing()
In [2]: controller.power.disable_tracing()
```

These log messages includes other trace loggers that log messages with `power` as the
These log messages include other trace loggers that log messages with `power` as the
`topic`, so they also appear automatically, so the log messages show changes to the
attribute throughout the stack: the query to the device and its response, the value the
attribute is set to, and the value that the PV in the EPICS CA transport is set to.
Expand All @@ -484,7 +483,7 @@ The `Tracer` can also be used as a module-level instance for use in free functio
```python
from fastcs.tracer import Tracer

tracer = Tracer(__name__)
tracer = Tracer()

def handle_attribute(attr):
tracer.log_event("Handling attribute", topic=attr)
Expand Down
5 changes: 1 addition & 4 deletions src/fastcs/attributes/attr_r.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,7 @@
from fastcs.attributes.attribute_io_ref import AttributeIORefT
from fastcs.attributes.util import AttrValuePredicate, PredicateEvent
from fastcs.datatypes import DataType, DType_T
from fastcs.logging import bind_logger

logger = bind_logger(logger_name=__name__)

from fastcs.logging import logger

AttrIOUpdateCallback = Callable[["AttrR[DType_T, Any]"], Awaitable[None]]
"""An AttributeIO callback that takes an AttrR and updates its value"""
Expand Down
3 changes: 0 additions & 3 deletions src/fastcs/attributes/attr_rw.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
from fastcs.attributes.attribute import AttributeAccessMode
from fastcs.attributes.attribute_io_ref import AttributeIORefT
from fastcs.datatypes import DataType, DType_T
from fastcs.logging import bind_logger

logger = bind_logger(logger_name=__name__)


class AttrRW(AttrR[DType_T, AttributeIORefT], AttrW[DType_T, AttributeIORefT]):
Expand Down
5 changes: 1 addition & 4 deletions src/fastcs/attributes/attr_w.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
from fastcs.attributes.attribute import Attribute, AttributeAccessMode
from fastcs.attributes.attribute_io_ref import AttributeIORefT
from fastcs.datatypes import DataType, DType_T
from fastcs.logging import bind_logger

logger = bind_logger(logger_name=__name__)

from fastcs.logging import logger

AttrOnPutCallback = Callable[["AttrW[DType_T, Any]", DType_T], Awaitable[None]]
"""Callbacks to be called when the setpoint of an attribute is changed"""
Expand Down
3 changes: 0 additions & 3 deletions src/fastcs/attributes/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@

from fastcs.attributes.attribute_io_ref import AttributeIORefT
from fastcs.datatypes import DataType, DType, DType_T
from fastcs.logging import bind_logger
from fastcs.tracer import Tracer

logger = bind_logger(logger_name=__name__)

AttributeAccessMode = Literal["r", "w", "rw"]


Expand Down
5 changes: 2 additions & 3 deletions src/fastcs/control_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@
from IPython.terminal.embed import InteractiveShellEmbed

from fastcs.controllers import BaseController, Controller
from fastcs.logging import bind_logger
from fastcs.logging import logger
from fastcs.methods import ScanCallback
from fastcs.tracer import Tracer
from fastcs.transports import ControllerAPI, Transport

tracer = Tracer(name=__name__)
logger = bind_logger(logger_name=__name__)
tracer = Tracer()


class FastCS:
Expand Down
4 changes: 1 addition & 3 deletions src/fastcs/controllers/base_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
from typing import _GenericAlias, get_args, get_origin, get_type_hints # type: ignore

from fastcs.attributes import AnyAttributeIO, Attribute, AttrR, AttrW, HintedAttribute
from fastcs.logging import bind_logger
from fastcs.logging import logger
from fastcs.methods import Command, Scan, UnboundCommand, UnboundScan
from fastcs.tracer import Tracer

logger = bind_logger(logger_name=__name__)


class BaseController(Tracer):
"""Base class for controllers
Expand Down
41 changes: 11 additions & 30 deletions src/fastcs/logging/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from ._graylog import GraylogStaticFields as GraylogStaticFields
from ._graylog import parse_graylog_env_fields as parse_graylog_env_fields
from ._graylog import parse_graylog_static_fields as parse_graylog_static_fields
from ._logging import Logger, LogLevel, _configure_logger
from ._logging import LogLevel, _configure_logger

logger = _logger.bind(logger_name="fastcs")
"""FastCS logger
Expand All @@ -21,33 +21,30 @@
and metrics in graylog.

It is best to keep the message short and use extra fields for additional information for
messages to be formatted nicely in the console. To add kwargs to format the message
without them appearing as extra fields, prepend the key with ``_``.
messages to be formatted nicely in the console.

.. code-block:: python

from fastcs.logging import logger

logger.info("PV put: {pv} = {value}", pv=pv, value=value)

By default messages will be logged with the name ``fastcs``. Within different modules
and classes it can be useful to override this name. This can be done with the ``bind``
method. To create a module logger with its name
To add kwargs to format the message without them appearing as extra fields, prepend the
key with ``_``.

By default messages will be logged with the name ``fastcs``. Within a driver it may be
useful to set a distinct logger name. This can be done with the ``bind`` method. To
create a new logger with the the name of the driver, use the following in a logging.py
module and use it throughout the package instead of the fastcs logger:

.. code-block:: python

from fastcs.logging import logger as _logger

logger = _logger.bind(logger_name=__name__)

or to create a class logger with its name

.. code-block:: python

self.logger = _logger.bind(logger_name=__class__.__name__)
logger = _logger.bind(logger_name="fastcs-driver")

As standard ``loguru`` supports ``trace`` level monitoring, but it should not be used in
fastcs. Instead there is a ``Tracer`` class for verbose logging with fine-grained
fastcs. Instead there is a `Tracer` class for verbose logging with fine-grained
controls that can be enabled by the user at runtime.

Use ``configure_logging`` to re-configure the logger at runtime. For more advanced
Expand All @@ -57,18 +54,6 @@
"""


def bind_logger(logger_name: str) -> Logger:
"""Create a wrapper of the singleton fastcs logger with the given name bound

The name will be displayed in all log messages from the returned wrapper.

See the docstring for ``fastcs.logging.logger`` for more information.

"""

return logger.bind(logger_name=logger_name)


def configure_logging(
level: LogLevel = LogLevel.INFO,
graylog_endpoint: GraylogEndpoint | None = None,
Expand All @@ -94,10 +79,6 @@ def configure_logging(
)


# Configure logger with defaults - INFO level and disabled
_configure_logger(logger)


class _StdLoggingInterceptHandler(logging.Handler):
"""A std logging handler to forward messages to loguru with nice formatting."""

Expand Down
5 changes: 3 additions & 2 deletions src/fastcs/logging/_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def format_record(record) -> str:
time = f"{_time}{_timezone}"

name = record["extra"].pop("logger_name", None) or record["name"]
message_pad = max(0, 100 - len(name))

sep = "<white>,</white> "
if "extra" in record:
Expand All @@ -108,8 +109,8 @@ def format_record(record) -> str:

return f"""\
<level>[{time} {record["level"].name[0]}]</level> \
{record["message"]:<80} \
<green>[{name}]</green> \
{record["message"]:<{message_pad}} \
<green>{name} [{record["file"].path}:{record["line"]}]</green> \
{extras}
{{exception}}\
"""
Expand Down
3 changes: 1 addition & 2 deletions src/fastcs/methods/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
from types import MethodType
from typing import TYPE_CHECKING

from fastcs.logging import bind_logger
from fastcs.logging import logger
from fastcs.methods.method import Controller_T, Method

if TYPE_CHECKING:
from fastcs.controllers import BaseController # noqa: F401

logger = bind_logger(logger_name=__name__)

UnboundCommandCallback = Callable[[Controller_T], Coroutine[None, None, None]]
"""A Command callback that is unbound and must be called with a `Controller` instance"""
Expand Down
3 changes: 1 addition & 2 deletions src/fastcs/methods/scan.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@
from types import MethodType
from typing import TYPE_CHECKING

from fastcs.logging import bind_logger
from fastcs.logging import logger
from fastcs.methods.method import Controller_T, Method

if TYPE_CHECKING:
from fastcs.controllers import BaseController # noqa: F401

logger = bind_logger(logger_name=__name__)

UnboundScanCallback = Callable[[Controller_T], Coroutine[None, None, None]]
"""A Scan callback that is unbound and must be called with a `Controller` instance"""
Expand Down
Loading