Browse Source

Add logging

Bastien Sevajol 7 years ago
parent
commit
a2e6fd5436

+ 15 - 0
sandbox/engulf/behaviour.py View File

@@ -11,6 +11,13 @@ class GrassGrownUp(Event):
11 11
         self.subject_id = subject_id
12 12
         self.density = density
13 13
 
14
+    def repr_debug(self) -> str:
15
+        return '{}: subject_id:{}, density:{}'.format(
16
+            self.__class__.__name__,
17
+            self.subject_id,
18
+            self.density,
19
+        )
20
+
14 21
 
15 22
 class GrassSpawn(Event):
16 23
     def __init__(self, subject_id, position, density, *args, **kwargs):
@@ -19,6 +26,14 @@ class GrassSpawn(Event):
19 26
         self.position = position
20 27
         self.density = density
21 28
 
29
+    def repr_debug(self) -> str:
30
+        return '{}: subject_id:{}, position:{}, density:{}'.format(
31
+            self.__class__.__name__,
32
+            self.subject_id,
33
+            self.position,
34
+            self.density,
35
+        )
36
+
22 37
 
23 38
 class GrassSpotablePositionsMechanism(SimulationMechanism):
24 39
     parallelizable = True

+ 21 - 4
sandbox/engulf/run.py View File

@@ -21,12 +21,16 @@ Engulf is simulation containing:
21 21
 import os
22 22
 import sys
23 23
 
24
+import logging
25
+
24 26
 synergine2_ath = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../'))
25 27
 sys.path.append(synergine2_ath)
26 28
 
27 29
 from random import randint, seed
28 30
 from sandbox.engulf.behaviour import GrassGrownUp, GrassSpawn, GrassSpawnBehaviour
29 31
 
32
+from synergine2.config import Config
33
+from synergine2.log import get_default_logger
30 34
 from sandbox.engulf.subject import Cell, Grass, COLLECTION_GRASS
31 35
 from synergine2.core import Core
32 36
 from synergine2.cycle import CycleManager
@@ -140,12 +144,25 @@ def main():
140 144
     )
141 145
     simulation.subjects = subjects
142 146
 
147
+    config = Config()
148
+    logger = get_default_logger(level=logging.DEBUG)
149
+    logger.debug('HELLO')
143 150
     core = Core(
151
+        config=config,
152
+        logger=logger,
144 153
         simulation=simulation,
145
-        cycle_manager=CycleManager(simulation=simulation),
146
-        terminal_manager=TerminalManager([GameTerminal(
147
-            asynchronous=False,
148
-        )]),
154
+        cycle_manager=CycleManager(
155
+            config=config,
156
+            logger=logger,
157
+            simulation=simulation,
158
+        ),
159
+        terminal_manager=TerminalManager(
160
+            config=config,
161
+            logger=logger,
162
+            terminals=[GameTerminal(
163
+                asynchronous=False,
164
+            )]
165
+        ),
149 166
         cycles_per_seconds=1,
150 167
     )
151 168
     core.run()

+ 6 - 0
synergine2/base.py View File

@@ -0,0 +1,6 @@
1
+# coding: utf-8
2
+
3
+
4
+class BaseObject(object):
5
+    def repr_debug(self) -> str:
6
+        return type(self).__name__

+ 5 - 0
synergine2/config.py View File

@@ -0,0 +1,5 @@
1
+# coding: utf-8
2
+
3
+
4
+class Config(dict):
5
+    pass

+ 16 - 2
synergine2/core.py View File

@@ -1,6 +1,9 @@
1 1
 # coding: utf-8
2 2
 import time
3
+
4
+from synergine2.config import Config
3 5
 from synergine2.cycle import CycleManager
6
+from synergine2.log import SynergineLogger
4 7
 from synergine2.simulation import Simulation
5 8
 from synergine2.terminals import TerminalManager
6 9
 from synergine2.terminals import TerminalPackage
@@ -9,11 +12,15 @@ from synergine2.terminals import TerminalPackage
9 12
 class Core(object):
10 13
     def __init__(
11 14
         self,
15
+        config: Config,
16
+        logger: SynergineLogger,
12 17
         simulation: Simulation,
13 18
         cycle_manager: CycleManager,
14 19
         terminal_manager: TerminalManager=None,
15 20
         cycles_per_seconds: int=1,
16 21
     ):
22
+        self.config = config
23
+        self.logger = logger
17 24
         self.simulation = simulation
18 25
         self.cycle_manager = cycle_manager
19 26
         self.terminal_manager = terminal_manager or TerminalManager([])
@@ -21,12 +28,14 @@ class Core(object):
21 28
         self._current_cycle_start_time = None
22 29
 
23 30
     def run(self):
31
+        self.logger.info('Run core')
24 32
         try:
25 33
             self.terminal_manager.start()
26 34
 
27 35
             start_package = TerminalPackage(
28 36
                 subjects=self.simulation.subjects,
29 37
             )
38
+            self.logger.info('Send start package to terminals')
30 39
             self.terminal_manager.send(start_package)
31 40
 
32 41
             while True:
@@ -59,14 +68,19 @@ class Core(object):
59 68
         self.terminal_manager.stop()
60 69
 
61 70
     def _start_cycle(self):
62
-        self._current_cycle_start_time = time.time()
71
+        time_ = time.time()
72
+        self.logger.info('Start cycle at time {}'.format(time_))
73
+        self._current_cycle_start_time = time_
63 74
 
64 75
     def _end_cycle(self) -> None:
65 76
         """
66 77
         Make a sleep if cycle duration take less time of wanted (see
67 78
         cycles_per_seconds constructor parameter)
68 79
         """
69
-        cycle_duration = time.time() - self._current_cycle_start_time
80
+        time_ = time.time()
81
+        self.logger.info('End of cycle at time {}'.format(time_))
82
+        cycle_duration = time_ - self._current_cycle_start_time
70 83
         sleep_time = self._loop_delta - cycle_duration
84
+        self.logger.info('Sleep time is {}'.format(sleep_time))
71 85
         if sleep_time > 0:
72 86
             time.sleep(sleep_time)

+ 182 - 12
synergine2/cycle.py View File

@@ -1,8 +1,11 @@
1 1
 # coding: utf-8
2 2
 import multiprocessing
3 3
 
4
+from synergine2.config import Config
5
+from synergine2.log import SynergineLogger
4 6
 from synergine2.processing import ProcessManager
5
-from synergine2.simulation import Subject, SimulationMechanism, SimulationBehaviour
7
+from synergine2.simulation import SimulationMechanism
8
+from synergine2.simulation import SimulationBehaviour
6 9
 from synergine2.simulation import Simulation
7 10
 from synergine2.simulation import SubjectBehaviour
8 11
 from synergine2.simulation import SubjectMechanism
@@ -13,6 +16,8 @@ from synergine2.utils import ChunkManager
13 16
 class CycleManager(object):
14 17
     def __init__(
15 18
             self,
19
+            config: Config,
20
+            logger: SynergineLogger,
16 21
             simulation: Simulation,
17 22
             process_manager: ProcessManager=None,
18 23
     ):
@@ -22,6 +27,8 @@ class CycleManager(object):
22 27
                 chunk_manager=ChunkManager(multiprocessing.cpu_count()),
23 28
             )
24 29
 
30
+        self.config = config
31
+        self.logger = logger
25 32
         self.simulation = simulation
26 33
         self.process_manager = process_manager
27 34
         self.current_cycle = -1
@@ -34,17 +41,26 @@ class CycleManager(object):
34 41
             self.first_cycle = False
35 42
         self.current_cycle += 1
36 43
 
44
+        self.logger.info('Process cycle {}'.format(self.current_cycle))
45
+
37 46
         events = []
38 47
         # TODO: gestion des behaviours non parallelisables
39 48
         # TODO: Proposer des ordres d'execution
40 49
         events.extend(self._get_subjects_events())
41 50
         events.extend(self._get_simulation_events())
42 51
 
52
+        self.logger.info('Cycle {} generate {} events'.format(
53
+            str(self.current_cycle),
54
+            str(len(events)),
55
+        ))
43 56
         return events
44 57
 
45 58
     def _get_simulation_events(self) -> [Event]:
46 59
         events = []
47 60
         results = {}
61
+
62
+        self.logger.info('Process simulation events')
63
+
48 64
         results_by_processes = self.process_manager.execute_jobs(
49 65
             data=self.simulation,
50 66
             job_maker=self.simulation_computing,
@@ -57,28 +73,64 @@ class CycleManager(object):
57 73
                     results.get(behaviour_class),
58 74
                 )
59 75
 
76
+        self.logger.info('Simulation generate {} behaviours'.format(len(results)))
77
+
60 78
         # Make events
61 79
         for behaviour_class, behaviour_data in results.items():
62
-            events.extend(self.simulation.behaviours[behaviour_class].action(behaviour_data))
80
+            behaviour_events = self.simulation.behaviours[behaviour_class].action(behaviour_data)
81
+            self.logger.info('{} behaviour generate {} events'.format(
82
+                str(behaviour_class),
83
+                str(len(behaviour_events)),
84
+            ))
85
+
86
+            if self.logger.is_debug:
87
+                self.logger.debug('{} behaviour generated events: {}'.format(
88
+                    str(behaviour_class),
89
+                    str([e.repr_debug() for e in behaviour_events]),
90
+                ))
63 91
 
92
+            events.extend(behaviour_events)
93
+
94
+        self.logger.info('Simulation behaviours generate {} events'.format(len(events)))
64 95
         return events
65 96
 
66 97
     def _get_subjects_events(self) -> [Event]:
67 98
         events = []
99
+        results = {}
100
+
101
+        self.logger.info('Process subjects events')
102
+
68 103
         results_by_processes = self.process_manager.chunk_and_execute_jobs(
69 104
             data=self.simulation.subjects,
70 105
             job_maker=self.subjects_computing,
71 106
         )
72
-        results = {}
107
+
73 108
         for process_results in results_by_processes:
74 109
             results.update(process_results)
75
-        for subject in self.simulation.subjects[:]:  # Duplicate list to prevent conflicts with behaviours subjects manipulations
110
+
111
+        # Duplicate list to prevent conflicts with behaviours subjects manipulations
112
+        for subject in self.simulation.subjects[:]:
76 113
             for behaviour_class, behaviour_data in results.get(subject.id, {}).items():
77 114
                 # TODO: Ajouter une etape de selection des actions a faire (genre neuronnal)
78 115
                 # (genre se cacher et fuir son pas compatibles)
79
-                actions_events = subject.behaviours[behaviour_class].action(behaviour_data)
80
-                events.extend(actions_events)
116
+                behaviour_events = subject.behaviours[behaviour_class].action(behaviour_data)
117
+
118
+                self.logger.info('{} behaviour for subject {} generate {} events'.format(
119
+                    str(behaviour_class),
120
+                    str(subject.id),
121
+                    str(len(behaviour_events)),
122
+                ))
123
+
124
+                if self.logger.is_debug:
125
+                    self.logger.debug('{} behaviour for subject {} generated events: {}'.format(
126
+                        str(behaviour_class),
127
+                        str(subject.id),
128
+                        str([e.repr_debug() for e in behaviour_events]),
129
+                    ))
81 130
 
131
+                events.extend(behaviour_events)
132
+
133
+        self.logger.info('Subjects behaviours generate {} events'.format(len(events)))
82 134
         return events
83 135
 
84 136
     def simulation_computing(
@@ -87,19 +139,49 @@ class CycleManager(object):
87 139
             process_number,
88 140
             process_count,
89 141
     ):
142
+        self.logger.info('Simulation computing')
143
+
90 144
         # TODO: necessaire de passer simulation ?
91 145
         mechanisms = self.get_mechanisms_to_compute(simulation)
92 146
         mechanisms_data = {}
93 147
         behaviours_data = {}
94 148
 
149
+        self.logger.info('{} mechanisms to compute'.format(str(len(mechanisms))))
150
+        if self.logger.is_debug:
151
+            self.logger.debug('Mechanisms are: {}'.format(
152
+                str([m.repr_debug() for m in mechanisms])
153
+            ))
154
+
95 155
         for mechanism in mechanisms:
96
-            mechanisms_data[type(mechanism)] = mechanism.run(
156
+            mechanism_data = mechanism.run(
97 157
                 process_number=process_number,
98 158
                 process_count=process_count,
99 159
             )
100 160
 
101
-        for behaviour in self.get_behaviours_to_compute(simulation):
161
+            if self.logger.is_debug:
162
+                self.logger.debug('{} mechanism product data: {}'.format(
163
+                    type(mechanism).__name__,
164
+                    str(mechanism_data),
165
+                ))
166
+
167
+            mechanisms_data[type(mechanism)] = mechanism_data
168
+
169
+        behaviours = self.get_behaviours_to_compute(simulation)
170
+        self.logger.info('{} behaviours to compute'.format(str(len(behaviours))))
171
+
172
+        if self.logger.is_debug:
173
+            self.logger.debug('Behaviours are: {}'.format(
174
+                str([b.repr_debug() for b in behaviours])
175
+            ))
176
+
177
+        for behaviour in behaviours:
102 178
             behaviour_data = behaviour.run(mechanisms_data)  # TODO: Behaviours dependencies
179
+            if self.logger.is_debug:
180
+                self.logger.debug('{} behaviour produce data: {}'.format(
181
+                    type(behaviour).__name__,
182
+                    behaviour_data,
183
+                ))
184
+
103 185
             if behaviour_data:
104 186
                 behaviours_data[type(behaviour)] = behaviour_data
105 187
 
@@ -112,17 +194,67 @@ class CycleManager(object):
112 194
             process_count=None,
113 195
     ):
114 196
         results = {}
197
+        self.logger.info('Subjects computing: {} subjects to compute'.format(str(len(subjects))))
198
+
115 199
         for subject in subjects:
116 200
             mechanisms = self.get_mechanisms_to_compute(subject)
201
+            if not mechanisms:
202
+                break
203
+
204
+            self.logger.info('Subject {}: {} mechanisms'.format(
205
+                str(subject.id),
206
+                str(len(mechanisms)),
207
+            ))
208
+
209
+            if self.logger.is_debug:
210
+                self.logger.info('Subject {}: mechanisms are: {}'.format(
211
+                    str(subject.id),
212
+                    str([m.repr_debug for m in mechanisms])
213
+                ))
214
+
117 215
             mechanisms_data = {}
118 216
             behaviours_data = {}
119 217
 
120 218
             for mechanism in mechanisms:
121
-                mechanisms_data[type(mechanism)] = mechanism.run()
219
+                mechanism_data = mechanism.run()
220
+                if self.logger.is_debug:
221
+                    self.logger.info('Subject {}: {} mechanisms produce data: {}'.format(
222
+                        str(subject.id),
223
+                        type(mechanism).__name__,
224
+                        str(mechanism_data),
225
+                    ))
226
+
227
+                mechanisms_data[type(mechanism)] = mechanism_data
228
+
229
+            if self.logger.is_debug:
230
+                self.logger.info('Subject {}: mechanisms data are: {}'.format(
231
+                    str(subject.id),
232
+                    str(mechanisms_data),
233
+                ))
234
+
235
+            subject_behaviours = self.get_behaviours_to_compute(subject)
236
+
237
+            self.logger.info('Subject {}: have {} behaviours'.format(
238
+                str(subject.id),
239
+                str(len(subject_behaviours)),
240
+            ))
241
+
242
+            for behaviour in subject_behaviours:
243
+                self.logger.info('Subject {}: run {} behaviour'.format(
244
+                    str(subject.id),
245
+                    str(type(behaviour)),
246
+                ))
122 247
 
123
-            for behaviour in self.get_behaviours_to_compute(subject):
124 248
                 # We identify behaviour data with it's class to be able to intersect it after subprocess data collect
125 249
                 behaviour_data = behaviour.run(mechanisms_data)  # TODO: Behaviours dependencies
250
+
251
+                if self.logger.is_debug:
252
+                    self.logger.debug('Subject {}: behaviour {} produce data: {}'.format(
253
+                        str(type(behaviour)),
254
+                        str(subject.id),
255
+                        str(behaviour_data),
256
+                    ))
257
+
126 258
                 if behaviour_data:
127 259
                     behaviours_data[type(behaviour)] = behaviour_data
128 260
 
@@ -157,6 +289,10 @@ class CycleManager(object):
157 289
         simulation_actions = simulation_actions or []
158 290
         subject_actions = subject_actions or []
159 291
         events = []
292
+        self.logger.info('Apply {} simulation_actions and {} subject_actions'.format(
293
+            len(simulation_actions),
294
+            len(subject_actions),
295
+        ))
160 296
 
161 297
         for subject_id, behaviours_and_data in subject_actions:
162 298
             subject = self.simulation.subjects.index.get(subject_id)
@@ -165,12 +301,46 @@ class CycleManager(object):
165 301
                     simulation=self.simulation,
166 302
                     subject=subject,
167 303
                 )
168
-                events.extend(behaviour.action(behaviour_data))
304
+                self.logger.info('Apply {} behaviour on subject {}'.format(
305
+                    str(behaviour_class),
306
+                    str(subject_id),
307
+                ))
308
+                if self.logger.is_debug:
309
+                    self.logger.debug('{} behaviour data is {}'.format(
310
+                        str(behaviour_class),
311
+                        str(behaviour_data),
312
+                    ))
313
+                behaviour_events = behaviour.action(behaviour_data)
314
+                self.logger.info('{} events from behaviour {} from subject {}'.format(
315
+                    len(behaviour_events),
316
+                    str(behaviour_class),
317
+                    str(subject_id),
318
+                ))
319
+                if self.logger.is_debug:
320
+                    self.logger.debug('Events from behaviour {} from subject {} are: {}'.format(
321
+                        str(behaviour_class),
322
+                        str(subject_id),
323
+                        str([e.repr_debug() for e in behaviour_events])
324
+                    ))
325
+                events.extend(behaviour_events)
169 326
 
170 327
         for behaviour_class, behaviour_data in simulation_actions:
171 328
             behaviour = behaviour_class(
172 329
                 simulation=self.simulation,
173 330
             )
174
-            events.extend(behaviour.action(behaviour_data))
175 331
 
332
+            self.logger.info('Apply {} simulation behaviour'.format(
333
+                str(behaviour_class),
334
+            ))
335
+
336
+            behaviour_events = behaviour.action(behaviour_data)
337
+            if self.logger.is_debug:
338
+                self.logger.debug('Events from behaviour {} are: {}'.format(
339
+                    str(behaviour_class),
340
+                    str([e.repr_debug() for e in behaviour_events])
341
+                ))
342
+
343
+            events.extend(behaviour_events)
344
+
345
+        self.logger.info('{} events generated'.format(len(events)))
176 346
         return events

+ 11 - 0
synergine2/exceptions.py View File

@@ -0,0 +1,11 @@
1
+# coding: utf-8
2
+
3
+
4
+class SynergineException(Exception):
5
+    pass
6
+
7
+
8
+class NotYetImplemented(SynergineException):
9
+    """
10
+    Like of NotImplementError. Use it to declare method to implement but only if wanted.
11
+    """

+ 27 - 0
synergine2/log.py View File

@@ -0,0 +1,27 @@
1
+# coding: utf-8
2
+import logging
3
+
4
+import sys
5
+import typing
6
+
7
+
8
+class SynergineLogger(logging.getLoggerClass()):
9
+    @property
10
+    def is_debug(self) -> bool:
11
+        return self.isEnabledFor(logging.DEBUG)
12
+
13
+
14
+def get_default_logger(name: str='synergine', level: int=logging.ERROR) -> SynergineLogger:
15
+    """
16
+    WARNING: Set global logging Logger class to SynergineLogger
17
+    """
18
+    logging.setLoggerClass(SynergineLogger)
19
+    logger = logging.getLogger(name)
20
+    logger = typing.cast(SynergineLogger, logger)
21
+    handler = logging.StreamHandler(sys.stdout)
22
+    formatter = logging.Formatter('%(asctime)s|%(name)s|%(process)d|%(levelname)s: %(message)s')
23
+    handler.setFormatter(formatter)
24
+    logger.addHandler(handler)
25
+    logger.setLevel(level)
26
+
27
+    return logger

+ 21 - 5
synergine2/simulation.py View File

@@ -1,6 +1,7 @@
1 1
 # coding: utf-8
2 2
 import collections
3 3
 
4
+from synergine2.base import BaseObject
4 5
 from synergine2.utils import get_mechanisms_classes
5 6
 
6 7
 
@@ -19,6 +20,15 @@ class Subject(object):
19 20
 
20 21
         self.initialize()
21 22
 
23
+    def __str__(self):
24
+        return self.id
25
+
26
+    def __repr__(self):
27
+        return '{}({})'.format(
28
+            type(self).__name__,
29
+            self.id,
30
+        )
31
+
22 32
     def initialize(self):
23 33
         for mechanism_class in get_mechanisms_classes(self):
24 34
             self.mechanisms[mechanism_class] = mechanism_class(
@@ -103,7 +113,7 @@ class Simulation(object):
103 113
             )
104 114
 
105 115
 
106
-class SubjectMechanism(object):
116
+class SubjectMechanism(BaseObject):
107 117
     def __init__(
108 118
             self,
109 119
             simulation: Simulation,
@@ -116,7 +126,7 @@ class SubjectMechanism(object):
116 126
         raise NotImplementedError()
117 127
 
118 128
 
119
-class SimulationMechanism(object):
129
+class SimulationMechanism(BaseObject):
120 130
     """If parallelizable behaviour, call """
121 131
     parallelizable = False
122 132
 
@@ -126,16 +136,22 @@ class SimulationMechanism(object):
126 136
     ):
127 137
         self.simulation = simulation
128 138
 
139
+    def repr_debug(self) -> str:
140
+        return self.__class__.__name__
141
+
129 142
     def run(self, process_id: int=None, process_count: int=None):
130 143
         raise NotImplementedError()
131 144
 
132 145
 
133
-class Event(object):
146
+class Event(BaseObject):
134 147
     def __init__(self, *args, **kwargs):
135 148
         pass
136 149
 
150
+    def repr_debug(self) -> str:
151
+        return self.__class__.__name__
152
+
137 153
 
138
-class SubjectBehaviour(object):
154
+class SubjectBehaviour(BaseObject):
139 155
     frequency = 1
140 156
     use = []
141 157
 
@@ -166,7 +182,7 @@ class SubjectBehaviour(object):
166 182
         raise NotImplementedError()
167 183
 
168 184
 
169
-class SimulationBehaviour(object):
185
+class SimulationBehaviour(BaseObject):
170 186
     frequency = 1
171 187
     use = []
172 188
 

+ 61 - 5
synergine2/terminals.py View File

@@ -7,13 +7,17 @@ from multiprocessing import Process
7 7
 from queue import Empty
8 8
 
9 9
 import time
10
+
11
+from synergine2.base import BaseObject
12
+from synergine2.config import Config
13
+from synergine2.log import SynergineLogger
10 14
 from synergine2.simulation import Subject
11 15
 from synergine2.simulation import Event
12 16
 
13 17
 STOP_SIGNAL = '__STOP_SIGNAL__'
14 18
 
15 19
 
16
-class TerminalPackage(object):
20
+class TerminalPackage(BaseObject):
17 21
     def __init__(
18 22
             self,
19 23
             subjects: [Subject]=None,
@@ -34,6 +38,18 @@ class TerminalPackage(object):
34 38
         self.subject_actions = subject_actions or []
35 39
         self.is_cycle = is_cycle
36 40
 
41
+    def repr_debug(self) -> str:
42
+        subjects = self.subjects or []
43
+        return str(dict(
44
+            subjects=subjects,
45
+            add_subjects=[s.id for s in self.add_subjects],
46
+            remove_subjects=[s.id for s in self.remove_subjects],
47
+            events=[e.repr_debug() for e in self.events],
48
+            simulation_actions=['{}: {}'.format(a.__class__.__name__, p) for a, p in self.simulation_actions],
49
+            subject_actions=['{}: {}'.format(a.__class__.__name__, p) for a, p in self.subject_actions],
50
+            is_cycle=self.is_cycle,
51
+        ))
52
+
37 53
 
38 54
 class Terminal(object):
39 55
     # Default behaviour is to do nothing.
@@ -118,13 +134,22 @@ class Terminal(object):
118 134
 
119 135
 
120 136
 class TerminalManager(object):
121
-    def __init__(self, terminals: [Terminal]):
137
+    def __init__(
138
+        self,
139
+        config: Config,
140
+        logger: SynergineLogger,
141
+        terminals: [Terminal]
142
+    ):
143
+        self.config = config
144
+        self.logger = logger
122 145
         self.terminals = terminals
123 146
         self.outputs_queues = {}
124 147
         self.inputs_queues = {}
125 148
 
126 149
     def start(self) -> None:
150
+        self.logger.info('Start terminals')
127 151
         for terminal in self.terminals:
152
+            # TODO: logs
128 153
             output_queue = Queue()
129 154
             self.outputs_queues[terminal] = output_queue
130 155
 
@@ -142,7 +167,13 @@ class TerminalManager(object):
142 167
             output_queue.put(STOP_SIGNAL)
143 168
 
144 169
     def send(self, package: TerminalPackage):
170
+        self.logger.info('Send package to terminals')
171
+        if self.logger.is_debug:
172
+            self.logger.debug('Send package to terminals: {}'.format(
173
+                str(package.repr_debug()),
174
+            ))
145 175
         for terminal, output_queue in self.outputs_queues.items():
176
+            self.logger.info('Send package to terminal {}'.format(terminal.__class__.__name__))
146 177
             # Terminal maybe don't want all events, so reduce list of event
147 178
             # Thirst make a copy to personalize this package
148 179
             terminal_adapted_package = copy(package)
@@ -153,11 +184,22 @@ class TerminalManager(object):
153 184
                 if not terminal.accept_event(package_event):
154 185
                     terminal_adapted_package.events.remove(package_event)
155 186
 
187
+            if self.logger.is_debug:
188
+                self.logger.debug('Send package to terminal {}: {}'.format(
189
+                    terminal.__class__.__name__,
190
+                    terminal_adapted_package.repr_debug(),
191
+                ))
192
+
156 193
             output_queue.put(terminal_adapted_package)
157 194
 
158 195
     def receive(self) -> [TerminalPackage]:
196
+        self.logger.info('Receive terminals packages')
159 197
         packages = []
160 198
         for terminal, input_queue in self.inputs_queues.items():
199
+            self.logger.info('Receive terminal {} packages ({})'.format(
200
+                terminal.__class__.__name__,
201
+                'sync' if not terminal.asynchronous else 'async'
202
+            ))
161 203
             # When terminal is synchronous, wait it's cycle package
162 204
             if not terminal.asynchronous:
163 205
                 continue_ = True
@@ -166,14 +208,28 @@ class TerminalManager(object):
166 208
                     # In case where terminal send package before end of cycle
167 209
                     # management
168 210
                     continue_ = not package.is_cycle
211
+
212
+                    if self.logger.is_debug:
213
+                        self.logger.debug('Receive package from {}: {}'.format(
214
+                            terminal.__class__.__name__,
215
+                            str(package.repr_debug()),
216
+                        ))
217
+
169 218
                     packages.append(package)
170 219
             else:
171 220
                 try:
172 221
                     while True:
173
-                        packages.append(
174
-                            input_queue.get(block=False, timeout=None),
175
-                        )
222
+                        package = input_queue.get(block=False, timeout=None)
223
+
224
+                        if self.logger.is_debug:
225
+                            self.logger.debug('Receive package from {}: {}'.format(
226
+                                str(terminal),
227
+                                str(package.repr_debug()),
228
+                            ))
229
+
230
+                        packages.append(package)
176 231
                 except Empty:
177 232
                     pass  # Queue is empty
178 233
 
234
+        self.logger.info('{} package(s) received'.format(len(packages)))
179 235
         return packages