0
# File I/O and Logging
1
2
Comprehensive logging and replay system supporting multiple file formats with both reader and writer implementations. Enables recording CAN traffic for analysis, replay for testing, and conversion between different logging formats.
3
4
## Capabilities
5
6
### Generic Logging
7
8
Base logging functionality with automatic format detection and writer management.
9
10
```python { .api }
11
class Logger:
12
def __init__(self, filename: str):
13
"""
14
Create a logger that writes to the specified file.
15
Format determined by file extension.
16
17
Parameters:
18
- filename: Output file path (extension determines format)
19
"""
20
21
def __call__(self, msg: Message) -> None:
22
"""Log a message (callable interface for use with Notifier)."""
23
24
def stop(self) -> None:
25
"""Stop logging and close files."""
26
27
class BaseRotatingLogger:
28
"""Base class for size or time-based log rotation."""
29
30
class SizedRotatingLogger(BaseRotatingLogger):
31
def __init__(self, base_filename: str, max_bytes: int):
32
"""
33
Logger with automatic file rotation based on size.
34
35
Parameters:
36
- base_filename: Base name for log files
37
- max_bytes: Maximum size before rotation
38
"""
39
```
40
41
### Generic Playback
42
43
Message playback and synchronization utilities for replaying recorded CAN traffic.
44
45
```python { .api }
46
class LogReader:
47
def __init__(self, filename: str):
48
"""
49
Read messages from log file. Format auto-detected from extension.
50
51
Parameters:
52
- filename: Input file path
53
"""
54
55
def __iter__(self):
56
"""Iterate over messages in the log file."""
57
58
class MessageSync:
59
def __init__(self, messages, timestamps=None, gap=0.0001):
60
"""
61
Synchronize message playback with real-time or custom timing.
62
63
Parameters:
64
- messages: Iterable of messages
65
- timestamps: Custom timestamp sequence
66
- gap: Minimum gap between messages (seconds)
67
"""
68
```
69
70
### ASC Format (Vector)
71
72
Vector CANalyzer/CANoe ASC log format support.
73
74
```python { .api }
75
class ASCReader:
76
def __init__(self, filename: str):
77
"""Read Vector ASC log files."""
78
79
def __iter__(self):
80
"""Iterate over messages."""
81
82
class ASCWriter:
83
def __init__(self, filename: str, channel: int = 1):
84
"""
85
Write Vector ASC log files.
86
87
Parameters:
88
- filename: Output file path
89
- channel: CAN channel number for logs
90
"""
91
92
def on_message_received(self, msg: Message) -> None:
93
"""Write message to ASC file."""
94
```
95
96
### BLF Format (Vector Binary)
97
98
Vector Binary Logging Format for high-performance logging.
99
100
```python { .api }
101
class BLFReader:
102
def __init__(self, filename: str):
103
"""Read Vector BLF (Binary Logging Format) files."""
104
105
def __iter__(self):
106
"""Iterate over messages."""
107
108
class BLFWriter:
109
def __init__(self, filename: str, channel: int = 1):
110
"""
111
Write Vector BLF files.
112
113
Parameters:
114
- filename: Output file path
115
- channel: CAN channel number
116
"""
117
118
def on_message_received(self, msg: Message) -> None:
119
"""Write message to BLF file."""
120
```
121
122
### CSV Format
123
124
Comma-separated values format for easy analysis in spreadsheet applications.
125
126
```python { .api }
127
class CSVReader:
128
def __init__(self, filename: str):
129
"""Read CSV log files with standard CAN message format."""
130
131
def __iter__(self):
132
"""Iterate over messages."""
133
134
class CSVWriter:
135
def __init__(self, filename: str):
136
"""Write CSV log files with message details in columns."""
137
138
def on_message_received(self, msg: Message) -> None:
139
"""Write message to CSV file."""
140
```
141
142
### Candump Format (Linux)
143
144
Linux SocketCAN candump/canutils log format.
145
146
```python { .api }
147
class CanutilsLogReader:
148
def __init__(self, filename: str):
149
"""Read Linux candump format log files."""
150
151
def __iter__(self):
152
"""Iterate over messages."""
153
154
class CanutilsLogWriter:
155
def __init__(self, filename: str):
156
"""Write Linux candump format log files."""
157
158
def on_message_received(self, msg: Message) -> None:
159
"""Write message in candump format."""
160
```
161
162
### MF4 Format (ASAM MDF)
163
164
ASAM Measurement Data Format version 4 for automotive measurement data.
165
166
```python { .api }
167
class MF4Reader:
168
def __init__(self, filename: str):
169
"""Read ASAM MF4 measurement files."""
170
171
def __iter__(self):
172
"""Iterate over messages."""
173
174
class MF4Writer:
175
def __init__(self, filename: str):
176
"""Write ASAM MF4 measurement files."""
177
178
def on_message_received(self, msg: Message) -> None:
179
"""Write message to MF4 file."""
180
```
181
182
### SQLite Database
183
184
SQLite database format for efficient querying and analysis.
185
186
```python { .api }
187
class SqliteReader:
188
def __init__(self, filename: str):
189
"""Read messages from SQLite database."""
190
191
def __iter__(self):
192
"""Iterate over messages."""
193
194
class SqliteWriter:
195
def __init__(self, filename: str, table_name: str = 'messages'):
196
"""
197
Write messages to SQLite database.
198
199
Parameters:
200
- filename: Database file path
201
- table_name: Table name for messages
202
"""
203
204
def on_message_received(self, msg: Message) -> None:
205
"""Write message to database."""
206
```
207
208
### TRC Format
209
210
TRC trace format support with version selection.
211
212
```python { .api }
213
class TRCFileVersion(Enum):
214
"""TRC file format versions."""
215
V1_0 = "1.0"
216
V1_1 = "1.1"
217
V2_0 = "2.0"
218
219
class TRCReader:
220
def __init__(self, filename: str):
221
"""Read TRC trace files."""
222
223
def __iter__(self):
224
"""Iterate over messages."""
225
226
class TRCWriter:
227
def __init__(self, filename: str, version: TRCFileVersion = TRCFileVersion.V2_0):
228
"""
229
Write TRC trace files.
230
231
Parameters:
232
- filename: Output file path
233
- version: TRC format version
234
"""
235
236
def on_message_received(self, msg: Message) -> None:
237
"""Write message to TRC file."""
238
```
239
240
### Console Output
241
242
Print messages to console for real-time monitoring.
243
244
```python { .api }
245
class Printer:
246
def __init__(self, filename=None):
247
"""
248
Print messages to console or file.
249
250
Parameters:
251
- filename: Optional file for output (None for stdout)
252
"""
253
254
def on_message_received(self, msg: Message) -> None:
255
"""Print message in human-readable format."""
256
```
257
258
### Format Registry
259
260
Automatic format detection and writer/reader selection.
261
262
```python { .api }
263
MESSAGE_READERS: dict[str, type]
264
"""Maps file extensions to reader classes."""
265
266
MESSAGE_WRITERS: dict[str, type]
267
"""Maps file extensions to writer classes."""
268
```
269
270
## Usage Examples
271
272
### Basic Logging
273
274
```python
275
import can
276
277
# Create bus and logger
278
bus = can.Bus(channel='can0', interface='socketcan')
279
logger = can.Logger('traffic.blf')
280
281
# Log messages manually
282
for _ in range(100):
283
msg = bus.recv(timeout=1.0)
284
if msg:
285
logger(msg)
286
287
logger.stop()
288
bus.shutdown()
289
```
290
291
### Automatic Logging with Notifier
292
293
```python
294
import can
295
import time
296
297
bus = can.Bus(channel='can0', interface='socketcan')
298
299
# Multiple loggers simultaneously
300
loggers = [
301
can.Logger('traffic.blf'), # Binary format
302
can.Logger('traffic.asc'), # ASCII format
303
can.Logger('traffic.csv'), # CSV format
304
can.Printer() # Console output
305
]
306
307
# Start logging
308
notifier = can.Notifier(bus, loggers)
309
310
# Let it log for 30 seconds
311
time.sleep(30)
312
313
# Stop logging
314
notifier.stop()
315
for logger in loggers:
316
if hasattr(logger, 'stop'):
317
logger.stop()
318
319
bus.shutdown()
320
```
321
322
### Log Playback
323
324
```python
325
import can
326
import time
327
328
# Read from log file
329
log_reader = can.LogReader('recorded_traffic.blf')
330
331
# Create playback bus
332
bus = can.Bus(channel='test', interface='virtual')
333
334
# Replay messages with original timing
335
for msg in can.MessageSync(log_reader, gap=0.0001):
336
bus.send(msg)
337
# MessageSync handles timing automatically
338
339
bus.shutdown()
340
```
341
342
### Log Conversion
343
344
```python
345
import can
346
347
# Convert between formats
348
input_reader = can.io.ASCReader('input.asc')
349
output_writer = can.io.BLFWriter('output.blf')
350
351
for msg in input_reader:
352
output_writer.on_message_received(msg)
353
354
output_writer.stop()
355
```
356
357
### Size-Based Log Rotation
358
359
```python
360
import can
361
import time
362
363
bus = can.Bus(channel='can0', interface='socketcan')
364
365
# Rotate logs every 10MB
366
rotating_logger = can.SizedRotatingLogger(
367
base_filename='can_traffic.blf',
368
max_bytes=10 * 1024 * 1024 # 10MB
369
)
370
371
notifier = can.Notifier(bus, [rotating_logger])
372
373
# Log for extended period with automatic rotation
374
time.sleep(3600) # 1 hour
375
376
notifier.stop()
377
rotating_logger.stop()
378
bus.shutdown()
379
```
380
381
### SQLite Analysis
382
383
```python
384
import can
385
import sqlite3
386
387
# Log to SQLite database
388
bus = can.Bus(channel='can0', interface='socketcan')
389
db_logger = can.SqliteWriter('can_data.db')
390
391
notifier = can.Notifier(bus, [db_logger])
392
time.sleep(60) # Log for 1 minute
393
notifier.stop()
394
db_logger.stop()
395
bus.shutdown()
396
397
# Analyze logged data
398
conn = sqlite3.connect('can_data.db')
399
cursor = conn.cursor()
400
401
# Find most common message IDs
402
cursor.execute("""
403
SELECT arbitration_id, COUNT(*) as count
404
FROM messages
405
GROUP BY arbitration_id
406
ORDER BY count DESC
407
LIMIT 10
408
""")
409
410
for row in cursor.fetchall():
411
print(f"ID: 0x{row[0]:X}, Count: {row[1]}")
412
413
conn.close()
414
```
415
416
## Types
417
418
```python { .api }
419
from typing import Dict, Type, Iterator, Optional, Any
420
from abc import ABC, abstractmethod
421
from enum import Enum
422
423
class Listener(ABC):
424
"""Base class for message listeners/loggers."""
425
426
@abstractmethod
427
def on_message_received(self, msg: Message) -> None: ...
428
429
def stop(self) -> None: ...
430
431
# Format registries
432
MESSAGE_READERS: Dict[str, Type] = {
433
'.asc': ASCReader,
434
'.blf': BLFReader,
435
'.csv': CSVReader,
436
'.log': CanutilsLogReader,
437
'.mf4': MF4Reader,
438
'.db': SqliteReader,
439
'.trc': TRCReader
440
}
441
442
MESSAGE_WRITERS: Dict[str, Type] = {
443
'.asc': ASCWriter,
444
'.blf': BLFWriter,
445
'.csv': CSVWriter,
446
'.log': CanutilsLogWriter,
447
'.mf4': MF4Writer,
448
'.db': SqliteWriter,
449
'.trc': TRCWriter
450
}
451
```