You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/explanation.md
+3-2Lines changed: 3 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -128,5 +128,6 @@ For a hands on explanation of these, please read the [how-to guide on how to use
128
128
129
129
## Further reading
130
130
131
-
- For how this routing model is implemented under the hood, see the [developer explanation](https://dune-daq.github.io/daqpytools/explanation).
132
-
- For how to configure ERS and advanced routing in practice, see [Configuring ERS](./how-to/configure-ers.md) and [Routing messages to specific handlers](./how-to/route-messages.md).
131
+
- For how this routing model is implemented under the hood, see the [developer explanation](https://dune-daq.github.io/daqpytools/dev).
132
+
- The architecture reference includes diagrams which explain exactly how the routing works. See [here](https://dune-daq.github.io/daqpytools/dev/reference/architecture/).
133
+
- For how to configure ERS and advanced routing in practice, see [Configuring ERS](./how-to/configure-ers.md) and [Routing messages to specific handlers](./how-to/route-messages.md).
Copy file name to clipboardExpand all lines: docs_dev/reference/architecture.md
+81Lines changed: 81 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -42,13 +42,15 @@ When you call `log.info("something")`, here's the actual flow:
42
42
1.**Python's logging creates a `LogRecord`** with your message, severity, and any `extra` metadata
43
43
44
44
2.**Logger-level filters run first** (e.g., `ThrottleFilter`):
45
+
45
46
- If any filter returns `False`, the record stops here
46
47
- It never reaches handlers
47
48
- This is where global concerns like throttling happen
48
49
49
50
3.**Record is offered to each attached handler**
50
51
51
52
4.**Each handler's `HandleIDFilter` decides** whether to emit:
53
+
52
54
- The filter calls the routing strategy to resolve `allowed_handlers`:
53
55
- If `extra["handlers"]` is present, use it
54
56
- Otherwise use the fallback set
@@ -57,8 +59,87 @@ When you call `log.info("something")`, here's the actual flow:
57
59
- Non-empty = emit; empty = drop the record
58
60
59
61
5.**Format and emit** (if the record passed the filter):
62
+
60
63
- The handler formats it and emits (to file, stdout, kafka, etc.)
61
64
62
65
This two-stage filtering is key: logger-level filters decide "should ANY handler see this?" while handler-level filters decide "should THIS handler see this?"
63
66
64
67

68
+
69
+
70
+
### Practical example
71
+
72
+
Here the practical logic flow is explained more with demonstrations.
73
+
74
+
Lets say we have a logger that contains both a Rich and File handler, and we explicitly define what the fallback handlers is.
75
+
76
+
```python
77
+
from daqpytools.logging import add_handler, HandlerType, get_daq_logger
78
+
log = get_daq_logger(
79
+
"myapp",
80
+
log_level="INFO",
81
+
rich_handler=False, # deliberately don't add handlers, we'll do it manually
And now we investigate what happens if we send a log message with `log.info("msg", extra={"handlers": HandlerType.Rich})`. Here, the log message will be sent to both the Rich and File handlers for processing. In each handler, it will check to see what the allowed handlers are (lets call it `handlers_to_check_against`). In this case, since we set it explicitly when sending the mesage, `handlers_to_check_against = HandlerType.Rich`.
99
+
100
+
For the Rich handler, it checks if the Rich handler is in the `handlers_to_check_against` list. In this case, it is, so it will transmit.
101
+
102
+
For the File handler, it will check if the File handler is in the `handlers_to_check_against` list. In this case, the file handler is _not_ in the list so it will not transmit the record.
103
+
104
+
105
+
Using the same setup, lets investigate what happens if we send a log a log message with `log.info("msg")`. In this case, the handlers keyword is not explicitly populated.
106
+
107
+
In this example, the record is sent independently to the two handlers. Since the handlers keyword is not set, the logic that populates `handlers_to_check_against` will check the `fallback_handlers` that are attached to each handler.
108
+
109
+
In the rich handler, we have initialised it with a fallback_handler of `HandlerType.Rich`, so `handlers_to_check_against = fallback_handler = HandlerType.Rich`. The rich handler will then check if the rich handler is in the `handlers_to_check_against`, which it is. So it will transmit the record.
110
+
111
+
In the file handler, we have initialised it with a fallback_handler of `HandlerType.File`, so `handlers_to_check_against = fallback_handler = HandlerType.File`. The file handler will then check if the file handler is in the `handlers_to_check_against`, which it is. So it will transmit the record.
112
+
113
+
This sounds a bit circular, but the reasoning for this should become clear in the case where we set different fallback handlers. Consider the following setup, where the only difference is that we set the the file handler's fallback_handler to `HandlerType.Unknown`:
114
+
115
+
116
+
```python
117
+
from daqpytools.logging import add_handler, HandlerType, get_daq_logger
118
+
log = get_daq_logger(
119
+
"myapp",
120
+
log_level="INFO",
121
+
rich_handler=False, # deliberately don't add handlers, we'll do it manually
Transmitting `log.info("msg", extra={"handlers": HandlerType.Rich})` will yield identical results to before. However, transmitting `log.info("msg")` yields different results.
138
+
139
+
As before, the log record is _independently_ fed towards the two handlers. For the rich handler, the flow is identical and so it will transmit.
140
+
141
+
In the case of the file handler, we have initialised it with a fallback_handler of `HandlerType.Unknown`, so `handlers_to_check_against = fallback_handler = HandlerType.Unknown`. The file handler will then check if the file handler is in the `handlers_to_check_against = HandlerType.Unknown`, which it is _not_ as `HandlerType.File` is not in `HandlerType.Unknown`. So it will **suppress** the record.
142
+
143
+
This has a very useful suppression effect, allowing the handlers to remain dormant by default. The only way to transmit with the file handler now is only if it was explicitly called for, by `log.info("msg", extra={"handlers": HandlerType.File})`
0 commit comments