Commit 17821802 authored by Piergiorgio Pancino's avatar Piergiorgio Pancino
Browse files

Mapping and logging documentation update an improvement

parent 1e928e6f
# Adding logging and mapping capabilities to Controller
To know how to use logging and mapping, see: [Shell
Logging](shell_logging.md)
To know how to use logging inside the shell, see: [Shell Logging](shell_logging.md)
To know more about mapping and how to use it, see: [Session map](dev_instance_map.md)
## Logging and Mapping
......@@ -13,12 +13,12 @@ device should register himself before gaining those features.
## How to Register a device
```python
from bliss.common import mapping
from bliss.common import session
from bliss.common.logtools import LogMixin
class MyController(LogMixin):
def __init__(self, *args, **kwargs):
mapping.register(self)
session.get_current().map.register(self)
...
self._logger.info("HI, I am born")
```
......@@ -36,7 +36,7 @@ will be used before device is mapped inside bliss device map.
### Register the device
This is done calling `mapping.register` passing at list the self
This is done calling `session.get_current().map.register` passing at list the self
parameter. If _logger methods are used before the registration this
will fail raising an exception; for this reason mapping.register
should be called as soon as possible.
......@@ -44,12 +44,12 @@ should be called as soon as possible.
## How to Create a nice device map
The map is in fact a Graph that wants to register every relevant
instance of a Beamline, including Controller, Connections, Devices,
instance of a Session, including Controller, Connections, Devices,
Axis, and so on.
When registering a device it is convenient to add as much information
as possible in order to have an usefull map that can be used to
represent the beamline or to apply any sort of desired handler.
represent the session or to apply any sort of desired handler.
For this reason is important to add:
......@@ -58,57 +58,147 @@ For this reason is important to add:
communication it will be a controller but also "comms".
* children_list: a list containing children istances as comms,
transactions, devices, axis
* tag: this should be the best suited name to represent the instance.
* tag: this should be the best suited name to represent the instance, if not
given instance.name will be used or id of the object
Some Examples:
### Example 1:
Here we have a Motor that is child of a controller
```python
mapping.register(self, parents_list=[self.controller], tag=str(self))
# self is motor instance (we are inside Axis.axis class)
# 'name' attribute is used as default to represent the object in the map
# 'tag' can be passed as kwarg to replace the name
# default is using name attribute of class
m = session.get_current().map
m.register(self, parents_list=[self.controller])
```
{% dot session_map_basic.svg
strict digraph {
rankdir="LR";
splines=false;
session;
devices;
controller [label="ee6beb9efb",];
axis [label="m0"]
controller -> axis;
devices -> controller;
session -> devices;
comms;
session -> comms;
counters;
session -> counters;
}
%}
### Example 2:
Here we have a controller with a child connection
```python
mapping.register(self, children_list=[self._cnx])
# self is test_controller
m = session.get_current().map
m.register(self, children_list=[self._cnx], tag='test controller')
```
{% dot session_map_basic.svg
strict digraph {
rankdir="LR";
splines=false;
session;
devices;
controller [label="test controller",];
conn [label="tcp_ip"]
controller -> conn;
devices -> controller;
session -> devices;
comms;
session -> comms;
counters;
session -> counters;
}
%}
### Example 3:
Here we have a serial connection that we also want to be child of
"beamline"->"comms"
"session"->"comms"
```python
mapping.register(self, parents_list=["comms"])
# registering m0, this normally is automatic, just as an example
# first passage we register m0, the controller and the connection
m = session.get_current().map
m.register(m0, parent_list=[m0.controller])
# in the second passage we register the TCP connection as a child of
# m0 and of comms
m = session.get_current().map
m.register(m0.conn, parent_list=[m0.controller, 'comms'])
```
{% dot session_map_basic.svg
strict digraph {
rankdir="LR";
splines=false;
session;
devices;
session -> devices;
comms;
session -> comms;
counters;
session -> counters;
controller;
devices -> controller;
m0;
controller -> m0;
tcp;
controller -> tcp;
comms -> tcp;
}
%}
### Example 4:
To explain the flexibility here we are mapping inside a Command class
(that is self) and `self._fd` is a child socket, in fact we are inside
Command but we are recording all links beetween them. The result will
something like this:
In fact, instances that will not have parents will be childs of
"session"->"devices" by default and later eventually remapped if we
register another instance as parent of `Command`.
```python
from bliss.common import session
m = session.get_current().map
self._fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
m.register(self._fd, parents_list=[self, "comms"], tag=f"Socket[{local_host}:{local_port}",
```
{% dot devices_mapping.svg
digraph devices_mapping{
rankdir="LR";
splines=false;
bl [label="beamline", shape="box"];
dev [label="devices", shape="box"];
cmd [label="Command", shape="box"];
sock [label="Socket", shape="box"];
session;
devices;
session -> devices;
comms;
session -> comms;
counters;
session -> counters;
command;
devices -> command;
sock [label="Socket[localhost:47319]"];
command -> sock;
comms -> sock;
bl -> dev;
dev -> cmd;
cmd -> sock;
bl -> sock;
}
%}
In fact, instances that will not have parents will be childs of
"beamline"->"devices" by default and later eventually remapped if we
register another instance as parent of `Command`.
```python
self._fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
mapping.register(self._fd, parents_list=[self, "comms"], tag=f"Socket[{local_host}:{local_port}",
```
### Final Considerations:
There is no problem if you want to register the same device twice or
even more times: it will automatically remap adding or removing links
......@@ -118,12 +208,18 @@ you want to register it a child.
The Bliss barebone map is something like this:
```
beamline -> devices
-> sessions
-> comms
-> counters
```
{% dot session_map_basic.svg
strict digraph {
node [label="\N"];
session;
devices;'
session -> devices;
comms;
session -> comms;
counters;
session -> counters;
}
%}
Those Graph nodes are in fact string and they constitute the root to
wich all other nodes will be attached.
......@@ -134,4 +230,50 @@ devices.
## Logging Instance Methods
TODO
Every instance that inherits from LogMixin and is registered gains a _logger instance that is in fact a python logging.Logger instance with some more powers.
This means that you will find a ._logger attribute attached to your instance that you can use to send messages and configure logging.
The most useful methods are:
* .debugon()
* .debugoff()
* .debug_data(message, data)
* .set_ascii_format()
* .set_hex_format()
### .debugon() and .debugoff()
Simply to set logging level to DEBUG or reset to default level.
### .debug_data, .set_ascii_format, .set_hex_format
The purpose of debug_data is to have a convenient way to debug aggregate data like string, bytestrings and dictionary. This is helpful for hardware comunication.
The first argument of debug_data is the user-readable message, the second is a string, bytestring or a dictionary.
set_ascii_format and set_hex_format methods allows to change the representation of data at runtime.
Let's do some examples:
### Passing a dictionary to debug_data:
```python
T_SESSION [14]: m0._logger.debug_data('Machine connection settings',{'ip':'10.81.0.23','hostname':'wcid00b'})
DEBUG 2019-05-17 14:58:27,861 session.devices.8d6318d713.axis.m0: Machine connection settings ip=10.81.0.23 ; hostname=wcid00b
```
### Simulating a message sent from `m0`.
Check how debug_data works and how we can change format from ascii to hex and viceversa, this can be done at runtime.
```python
TEST_SESSION [1]: m0._logger.debugon()
TEST_SESSION [2]: raw_msg = bytes([0,2,3,12,254,255,232,121,123,83,72])
TEST_SESSION [3]: m0._logger.set_ascii_format()
TEST_SESSION [4]: m0._logger.debug_data('Sending Data',raw_msg)
DEBUG 2019-05-17 15:24:26,231 session.devices.8d6318d.axis.m0: Sending Data bytes=11 b'\x00\x02\x03\x0c\xfe\xff\xe8y{SH'
TEST_SESSION [5]: m0._logger.set_hex_format()
TEST_SESSION [6]: m0._logger.debug_data('Sending Data',raw_msg)
DEBUG 2019-05-17 15:24:34,731 session.devices.8d6318d.axis.m0: Sending Data bytes=11 \x00\x02\x03\x0c\xfe\xff\xe8\x79\x7b\x53\x48
```
......@@ -7,20 +7,30 @@ There are two kind of logging in Bliss:
* *Module logging*
* *Instance logging*
more info about [Python logging module](https://docs.python.org/3/library/logging.html).
### Module logging
We can have a look at both with `lslog()`.
Module logging is the standard python "way of logging" in which every
*logger* has the same name that the python module which is producing
it.
```python
DEMO [2]: lslog()
The hierarchy is given by the files organization inside the Bliss
project folder.
BLISS MODULE LOGGERS
module level set
==================== ======== =====
bliss WARNING YES
bliss.config WARNING -
bliss.common.mapping WARNING -
bliss.scans WARNING -
bliss.shell WARNING -
bliss.standard WARNING -
`lslog()` allows to see the list of loggers in use.
BEAMLINE INSTANCE MAP LOGGERS
instance level set
================================================= ======== =====
beamline WARNING YES
beamline.comms WARNING -
beamline.counters WARNING -
beamline.devices WARNING -
beamline.sessions WARNING -
```
The relevant informations are:
......@@ -38,6 +48,17 @@ The relevant informations are:
* `-` if the level is inherited from the upper level
more info about [Python logging module](https://docs.python.org/3/library/logging.html).
### Module logging
Module-level logging is the standard python "way of logging" in which every
*logger* has the same name as the python module producing it.
The hierarchy is given by files organization inside Bliss project folder.
```python
DEMO [2]: lslog()
......@@ -53,11 +74,17 @@ bliss.standard WARNING -
...
```
Inside modules logger object are instantiated with the well known:
```python
import logging
logger = logging.getLogger(__name__)
```
Thiss will create a logger with a name that will be a commad separated folder/file name hierarchy.
### Instance logging
Same remarks for Instance logging which is a specific Bliss logging in
which every logger has a name that represents the instance hierarchy.
Instance-level logging allows to discriminate beetween different instances of the same class. With instance logging every device or instance has his own logger with a name that represents the conceptual hierarchy of the hardware/software stack.
```
DEMO [2]: lslog()
......@@ -92,8 +119,15 @@ beamline.sessions WARNING -
### Devices and instances
Probably the most convenient way to activate logging for a specific
device is from the `_logger` method of the device itself:
Activate logging can be done as following:
```
TEST_SESSION [1]: log.debugon('*s1d') # using glob pattern
NO bliss loggers found for [*s1d]
Set logger [beamline.devices.8d6318d713ee6be.axis.s1d] to DEBUG level
```
Or within the device itself:
```
BLISS [1]: m0 = config.get('m0')
......@@ -125,16 +159,30 @@ Activating debug from one specific device may not give the desired
informations as a device could be managed by a controller and a
controller may handle a communication.
To collect all informations activate debug at the higher level,
usually for the controller.
Sometimes what you will probably need is to activate debug from the controller level.
```
log.debugon('8d6318d7dde')
TEST_SESSION [4]: log.debugon('*8d6318d7*')
NO bliss loggers found for [*8d6318d7*]
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.hooked_error_m0] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.hooked_m0] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.hooked_m1] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.jogger] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.m0] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.m1] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.m2] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.omega] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.roby] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.s1b] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.s1d] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.s1f] to DEBUG level
Set logger [beamline.devices.8d6318d713ee6beb9efbb5be322b8dde.axis.s1u] to DEBUG level
```
## log commands
The class container for log commands.
The class instance for log commands.
### log.lslog() or lslog()
......@@ -205,11 +253,10 @@ beamline.counters DEBUG YES
### log.debugon()
`log.debugon("logger name or part of it")` activates debug for a
specific logger name, it will match also if is only a part of the
logger name is given.
`log.debugon("globname")` activates debug for a
specific logger name using glob pattern.
```pyton
```python
BLISS [22]: log.debugon('hooked')
NO bliss loggers found for [hooked]
Set logger [beamline.devices.8d6318d7dde.axis.hooked_error_m0] to DEBUG level
......@@ -220,20 +267,62 @@ Set logger [beamline.devices.8d6318d7dde.axis.hooked_m1] to DEBUG level
### log.debugoff()
```
log.debugoff("logger name or part of it")
log.debugoff("globname")
```
Like `debugon()` but sets the logging level for that logger name to
the global defined one.
Like `debugon()` but sets the logging level to global defined one.
```
BLISS [23]: log.debugoff('hooked')
BLISS [23]: log.debugoff('*hooked*')
NO bliss loggers found for [hooked]
Remove DEBUG level from logger [beamline.devices.8d6318d7de.axis.hooked_error_m0]
Remove DEBUG level from logger [beamline.devices.8d6318d7de.axis.hooked_m0]
Remove DEBUG level from logger [beamline.devices.8d6318d7de.axis.hooked_m1]
```
For details on how to implement logging in a Bliss module or
controller, see: [mapping and logging](dev_maplog_controller.md)
## How to log user shell commands
It is only a matter of activating the proper logger: bliss.shell.cli.repl
```python
BLISS [2]: log.debugon('bliss.shell.cli.repl')
Set logger [bliss.shell.cli.repl] to DEBUG level
NO map loggers found for [bliss.shell.cli.repl]
BLISS [3]: print('LogMe')
DEBUG 2019-05-17 13:09:32,628 bliss.shell.cli.repl: USER INPUT: print('LogMe')
LogMe
```
## Save log to File or other destinations
There are a lot of ways to accomplish this.
The easiest is to add a logging Handler to the root Logger.
This is accomplished using a normal python logging Handler taken from the standard library.
Logging could be initialized on bliss shell, but probably the best place to do this is in session configuration script.
```python
# Just near the end of your session_setup.py
from logging import getLogger, FileHandler, Formatter, DEBUG
rootlogger = getLogger() # getting root logger
filehandler = FileHandler('mylogfile.log') # creating a file handler
formatter = Formatter("%(asctime)s-%(name)s-%(lineno)d-%(msg)s-%(exc_info)s") # creating a formatter for file messages
filehandler.setFormatter(formatter) # filehandler will use the formatter
rootlogger.addHandler(filehandler) # adding the handler to the root logger
# Just after you can set debug level for some instances
log.debugon('*roby') # activating level using shell commands
roby._logger.debugon() # alternative way of activating
```
Another useful Handler is RotatingFileHandler:
```
from logging.handlers import RotatingFileHandler
# rotation of 10 log files with maximum size of 1Mb
rotatinghandler = RotatingFileHandler(‘mybliss.log’, maxBytes=1024*10**3, backupCount=10)
rootlogger.addHandler(rotatinghandler) # adding the handler to the root logger
```
......@@ -117,6 +117,7 @@ nav:
- Writing a temperature controller: dev_write_tempctrl.md
- Logging a controller: dev_maplog_controller.md
- Writing a shutter: dev_write_shutter.md
- Session Instance Map: dev_instance_map.md
- Counters: dev_ct.md
- Acquisition objects: dev_acq.md
- Tips'n'tricks: dev_tipsntricks.md
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment