Skip to content

Log

This module provides a custom logger class that extends the built-in Python logger.

The custom logger class provides additional functionality for tracking and logging variables to a CSV file. This can be useful for monitoring the values of variables during program execution, especially in long-running or complex applications. The logger class also supports different log levels and log formatting options.

The custom logger class is implemented as a singleton, ensuring that only one instance of the logger is created and used throughout the application. This helps to maintain a consistent logging configuration and avoid conflicts between multiple logger instances.

Class
  • Logger: Custom logger class that extends the built-in Python logger.
Enum
  • LogLevel: Enumerates the possible log levels (DEBUG, INFO, WARNING, ERROR, CRITICAL).
Globals
  • LOGGER: Global instance of the custom logger class available for use throughout the library
  • LOG_LEVEL: Dictionary mapping log level names to their corresponding integer values.

LogLevel

Bases: Enum

Enumerates the possible log levels.

Attributes:

Name Type Description
DEBUG int

Debug log level

INFO int

Info log level

WARNING int

Warning log level

ERROR int

Error log level

CRITICAL int

Critical log level

Examples:

>>> LogLevel.DEBUG
10
>>> LogLevel.INFO
20
Source code in onshape_robotics_toolkit\log.py
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class LogLevel(Enum):
    """
    Enumerates the possible log levels.

    Attributes:
        DEBUG (int): Debug log level
        INFO (int): Info log level
        WARNING (int): Warning log level
        ERROR (int): Error log level
        CRITICAL (int): Critical log level

    Examples:
        >>> LogLevel.DEBUG
        10
        >>> LogLevel.INFO
        20
    """

    DEBUG = logging.DEBUG
    INFO = logging.INFO
    WARNING = logging.WARNING
    ERROR = logging.ERROR
    CRITICAL = logging.CRITICAL

Logger

Bases: Logger

Represents a custom singleton logger class that extends the built-in Python logger. The logger provides additional functionality for tracking and logging variables to a CSV file. It supports different log levels and log formatting options.

Parameters:

Name Type Description Default
log_path str

The path to save log files.

'./'
log_format str

The log message format.

'[%(asctime)s] %(levelname)s: %(message)s'
file_level LogLevel

The log level for file output.

DEBUG
stream_level LogLevel

The log level for console output.

INFO
file_max_bytes int

The maximum size of the log file in bytes before rotation.

0
file_backup_count int

The number of backup log files to keep.

5
file_name Union[str, None]

The base name for the log file.

None
buffer_size int

The maximum number of log entries to buffer before writing to the CSV file.

1000
Properties
  • file_path: The path to the log file.
  • buffer_size: The maximum number of log entries to buffer.
  • file_level: The log level for file output.
  • stream_level: The log level for console output.
  • file_max_bytes: The maximum size of the log file in bytes before rotation.
  • file_backup_count: The number of backup log files to keep.

Methods:

Name Description
- **track_variable**

Track a variable for logging.

- **untrack_variable**

Stop tracking a variable.

- **flush_buffer**

Write the buffered log entries to the CSV file.

- **reset**

Reset the logger state.

- **close**

Close the logger and flush any remaining log entries.

- **debug**

Log a debug message.

- **info**

Log an info message.

- **warning**

Log a warning message.

- **error**

Log an error message.

- **critical**

Log a critical message.

- **log**

Log a message at a specific log level.

Examples:

>>> logger = Logger()
>>> logger.info("This is an info message")
[2022-01-01 12:00:00] INFO: This is an info message
>>> logger.debug("This is a debug message")
[2022-01-01 12:00:00] DEBUG: This is a debug message
>>> logger.track_variable(lambda: 42, "answer")
>>> logger.update()
>>> logger.flush_buffer()
Source code in onshape_robotics_toolkit\log.py
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
class Logger(logging.Logger):
    """
    Represents a custom singleton logger class that extends the built-in Python logger. The logger provides additional
    functionality for tracking and logging variables to a CSV file. It supports different log levels and log formatting
    options.

    Args:
        log_path (str): The path to save log files.
        log_format (str): The log message format.
        file_level (LogLevel): The log level for file output.
        stream_level (LogLevel): The log level for console output.
        file_max_bytes (int): The maximum size of the log file in bytes before rotation.
        file_backup_count (int): The number of backup log files to keep.
        file_name (Union[str, None]): The base name for the log file.
        buffer_size (int): The maximum number of log entries to buffer before writing to the CSV file.

    Properties:
        - **file_path**: The path to the log file.
        - **buffer_size**: The maximum number of log entries to buffer.
        - **file_level**: The log level for file output.
        - **stream_level**: The log level for console output.
        - **file_max_bytes**: The maximum size of the log file in bytes before rotation.
        - **file_backup_count**: The number of backup log files to keep.

    Methods:
        - **track_variable**: Track a variable for logging.
        - **untrack_variable**: Stop tracking a variable.
        - **flush_buffer**: Write the buffered log entries to the CSV file.
        - **reset**: Reset the logger state.
        - **close**: Close the logger and flush any remaining log entries.
        - **debug**: Log a debug message.
        - **info**: Log an info message.
        - **warning**: Log a warning message.
        - **error**: Log an error message.
        - **critical**: Log a critical message.
        - **log**: Log a message at a specific log level.

    Examples:
        >>> logger = Logger()
        >>> logger.info("This is an info message")
        [2022-01-01 12:00:00] INFO: This is an info message
        >>> logger.debug("This is a debug message")
        [2022-01-01 12:00:00] DEBUG: This is a debug message

        >>> logger.track_variable(lambda: 42, "answer")
        >>> logger.update()
        >>> logger.flush_buffer()

    """

    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        else:
            print(f"Reusing existing Logger instance: {id(cls._instance)}")
        return cls._instance

    def __init__(
        self,
        log_path: str = "./",
        log_format: str = "[%(asctime)s] %(levelname)s: %(message)s",
        file_level: LogLevel = LogLevel.DEBUG,
        stream_level: LogLevel = LogLevel.INFO,
        file_max_bytes: int = 0,
        file_backup_count: int = 5,
        file_name: Union[str, None] = None,
        buffer_size: int = 1000,
    ) -> None:
        """
        Initialize the custom logger with the specified configuration.

        Args:
            log_path: The path to save log files.
            log_format: The log message format.
            file_level: The log level for file output.
            stream_level: The log level for console output.
            file_max_bytes: The maximum size of the log file in bytes before rotation.
            file_backup_count: The number of backup log files to keep.
            file_name: The base name for the log file.
            buffer_size: The maximum number of log entries to buffer before writing to the CSV file.
        """
        if not hasattr(self, "_initialized"):
            super().__init__(__name__)
            self._log_path = log_path
            self._log_format = log_format
            self._file_level = file_level
            self._stream_level = stream_level
            self._file_max_bytes = file_max_bytes
            self._file_backup_count = file_backup_count
            self._user_file_name = file_name

            self._file_path: str = ""
            self._csv_path: str = ""
            self._file: Optional[Any] = None
            self._writer = None
            self._is_logging = False
            self._header_written = False

            self._tracked_vars: dict[int, Callable[[], Any]] = {}
            self._var_names: dict[int, str] = {}
            self._buffer: deque[list[str]] = deque(maxlen=buffer_size)
            self._buffer_size: int = buffer_size

            self._setup_logging()
            self._initialized: bool = True
        else:
            self.set_file_name(file_name)
            self.set_file_level(file_level)
            self.set_stream_level(stream_level)
            self.set_format(log_format)
            self._file_max_bytes = file_max_bytes
            self._file_backup_count = file_backup_count
            self.set_buffer_size(buffer_size)

            self._log_path = log_path

    def _setup_logging(self) -> None:
        if not hasattr(self, "_stream_handler"):  # Prevent duplicate handlers
            self.setLevel(level=self._file_level.value)
            self._std_formatter = logging.Formatter(self._log_format)

            self._stream_handler = logging.StreamHandler()
            self._stream_handler.setLevel(level=self._stream_level.value)
            self._stream_handler.setFormatter(fmt=self._std_formatter)
            self.addHandler(hdlr=self._stream_handler)

    def _setup_file_handler(self) -> None:
        if not hasattr(self, "_file_handler"):  # Ensure file handler is added only once
            self._generate_file_paths()

            self._file_handler = RotatingFileHandler(
                filename=self._file_path,
                mode="w",
                maxBytes=self._file_max_bytes,
                backupCount=self._file_backup_count,
                encoding="utf-8",
            )
            self._file_handler.setLevel(level=self._file_level.value)
            self._file_handler.setFormatter(fmt=self._std_formatter)
            self.addHandler(hdlr=self._file_handler)

    def _ensure_file_handler(self):
        if not hasattr(self, "_file_handler"):
            self._setup_file_handler()

    def track_variable(self, var_func: Callable[[], Any], name: str) -> None:
        """
        Record the value of a variable and log it to a CSV file.

        Args:
            var_func: A function that returns the value of the variable.
            name: The name of the variable.

        Examples:
            >>> class MyClass:
            ...     def __init__(self):
            ...         self.value = 42
            >>> obj = MyClass()
            >>> LOGGER.track_variable(lambda: obj.value, "answer")
            >>> LOGGER.update()
            >>> LOGGER.flush_buffer()
        """

        var_id = id(var_func)
        self._tracked_vars[var_id] = var_func
        self._var_names[var_id] = name

    def untrack_variable(self, var_func: Callable[[], Any]) -> None:
        """
        Stop tracking a variable and remove it from the logger buffer.

        Args:
            var_func: The function used to track the variable.

        Examples:
            >>> class MyClass:
            ...     def __init__(self):
            ...         self.value = 42
            >>> obj = MyClass()
            >>> LOGGER.track_variable(lambda: obj.value, "answer")
            >>> LOGGER.update()
            >>> LOGGER.flush_buffer()
            >>> LOGGER.untrack_variable(lambda: obj.value)
        """
        var_id = id(var_func)
        self._tracked_vars.pop(var_id, None)
        self._var_names.pop(var_id, None)

    def __repr__(self) -> str:
        return f"Logger(file_path={self._file_path})"

    def set_file_name(self, file_name: Union[str, None]) -> None:
        """
        Set the base name for the log file.

        Args:
            file_name: The base name for the log file.

        Examples:
            >>> LOGGER.set_file_name("my_log_file")
            >>> LOGGER.file_path
            "./my_log_file.log"
        """
        # if filename has an extension, remove it
        if file_name is not None and "." in file_name:
            file_name = file_name.split(".")[0]

        self._user_file_name = file_name
        self._file_path = ""
        self._csv_path = ""

    def set_file_level(self, level: LogLevel) -> None:
        """
        Set the log level for file output.

        Args:
            level: The log level for file output.

        Examples:
            >>> LOGGER.set_file_level(LogLevel.INFO)
            >>> LOGGER.file_level
            LogLevel.INFO
            >>> LOGGER.debug("This is a debug message and will not be logged")
        """
        self._file_level = level
        if hasattr(self, "_file_handler"):
            self._file_handler.setLevel(level=level.value)

    def set_stream_level(self, level: LogLevel) -> None:
        """
        Set the log level for console output.

        Args:
            level: The log level for console output.

        Examples:
            >>> LOGGER.set_stream_level(LogLevel.INFO)
            >>> LOGGER.stream_level
            LogLevel.INFO
            >>> LOGGER.debug("This is a debug message and will not be streamed")
        """
        self._stream_level = level
        self._stream_handler.setLevel(level=level.value)

    def set_format(self, log_format: str) -> None:
        """
        Set the log message format. The format string uses the same syntax as the built-in Python logging module.

        Args:
            log_format: The log message format.

        Examples:
            >>> LOGGER.set_format("[%(asctime)s] %(levelname)s: %(message)s")
            >>> LOGGER.info("This is an info message")
            [2022-01-01 12:00:00] INFO: This is an info message
        """
        self._log_format = log_format
        self._std_formatter = logging.Formatter(log_format)
        if hasattr(self, "_file_handler"):
            self._file_handler.setFormatter(fmt=self._std_formatter)
        self._stream_handler.setFormatter(fmt=self._std_formatter)

    def set_buffer_size(self, buffer_size: int) -> None:
        """
        Set the maximum number of log entries to buffer before writing to the CSV file.

        Args:
            buffer_size: The maximum number of log entries to buffer.
        """
        self._buffer_size = buffer_size
        self._buffer = deque(self._buffer, maxlen=buffer_size)

    def update(self) -> None:
        """
        Update the logger by logging the current values of tracked variables to the buffer.

        Examples:
            >>> class MyClass:
            ...     def __init__(self):
            ...         self.value = 42
            >>> obj = MyClass()
            >>> LOGGER.track_variable(lambda: obj.value, "answer")
            >>> LOGGER.update()
        """
        if not self._tracked_vars:
            return

        data = []
        for _var_id, get_value in self._tracked_vars.items():
            value = get_value()
            data.append(str(value))

        self._buffer.append(data)

        if len(self._buffer) >= self._buffer_size:
            self.flush_buffer()

    def flush_buffer(self) -> None:
        """
        Write the buffered log entries to the CSV file.
        """
        if not self._buffer:
            return

        self._ensure_file_handler()

        if self._file is None:
            self._file = open(self._csv_path, "w", newline="")
            self._writer = csv.writer(self._file)

        if not self._header_written:
            self._write_header()

        self._writer.writerows(self._buffer)
        self._buffer.clear()
        self._file.flush()

    def _write_header(self) -> None:
        header = list(self._var_names.values())

        self._writer.writerow(header)  # type: ignore[assignment]
        self._header_written = True

    def _generate_file_paths(self) -> None:
        now = datetime.now()
        timestamp = now.strftime("%Y%m%d_%H%M%S")
        script_name = os.path.basename(__file__).split(".")[0]

        base_name = self._user_file_name if self._user_file_name else f"{script_name}_{timestamp}"

        file_path = os.path.join(self._log_path, base_name)
        self._file_path = file_path + ".log"
        self._csv_path = file_path + ".csv"

    def __enter__(self) -> "Logger":
        return self

    def __exit__(self, exc_type, exc_val, exc_tb) -> None:
        self.close()

    def reset(self) -> None:
        """
        Reset the logger state.
        """
        self._buffer.clear()
        self._tracked_vars.clear()
        self._var_names.clear()
        self._header_written = False
        if hasattr(self, "_file_handler"):
            self._file_handler.close()
            del self._file_handler

    def close(self) -> None:
        """
        Close the logger and flush any remaining log entries.

        Examples:
            >>> LOGGER.close()
            >>> LOGGER.info("This message will not be logged")
        """
        self.flush_buffer()
        if self._file:
            self._file.close()
            self._file = None
            self._writer = None

    def debug(self, msg, *args, **kwargs):
        self._ensure_file_handler()
        super().debug(msg, *args, **kwargs)

    def info(self, msg, *args, **kwargs):
        self._ensure_file_handler()
        super().info(msg, *args, **kwargs)

    def warning(self, msg, *args, **kwargs):
        self._ensure_file_handler()
        super().warning(msg, *args, **kwargs)

    def error(self, msg, *args, **kwargs):
        self._ensure_file_handler()
        super().error(msg, *args, **kwargs)

    def critical(self, msg, *args, **kwargs):
        self._ensure_file_handler()
        super().critical(msg, *args, **kwargs)

    def log(self, level, msg, *args, **kwargs):
        self._ensure_file_handler()
        super().log(level, msg, *args, **kwargs)

    @property
    def file_path(self) -> str:
        """
        Get the path to the log file.
        """
        if self._file_path == "":
            self._generate_file_paths()
        return self._file_path

    @property
    def buffer_size(self) -> int:
        """
        Get the maximum number of log entries to buffer before writing to the CSV file.
        """
        return self._buffer_size

    @property
    def file_level(self) -> LogLevel:
        """
        Get the log level for file output (.log).
        """
        return self._file_level

    @property
    def stream_level(self) -> LogLevel:
        """
        Get the log level for console output.
        """
        return self._stream_level

    @property
    def file_max_bytes(self) -> int:
        """
        Get the maximum size of the log file in bytes before rotation.
        """
        return self._file_max_bytes

    @property
    def file_backup_count(self) -> int:
        """
        Get the number of backup log files to keep.
        """
        return self._file_backup_count

buffer_size: int property

Get the maximum number of log entries to buffer before writing to the CSV file.

file_backup_count: int property

Get the number of backup log files to keep.

file_level: LogLevel property

Get the log level for file output (.log).

file_max_bytes: int property

Get the maximum size of the log file in bytes before rotation.

file_path: str property

Get the path to the log file.

stream_level: LogLevel property

Get the log level for console output.

__init__(log_path='./', log_format='[%(asctime)s] %(levelname)s: %(message)s', file_level=LogLevel.DEBUG, stream_level=LogLevel.INFO, file_max_bytes=0, file_backup_count=5, file_name=None, buffer_size=1000)

Initialize the custom logger with the specified configuration.

Parameters:

Name Type Description Default
log_path str

The path to save log files.

'./'
log_format str

The log message format.

'[%(asctime)s] %(levelname)s: %(message)s'
file_level LogLevel

The log level for file output.

DEBUG
stream_level LogLevel

The log level for console output.

INFO
file_max_bytes int

The maximum size of the log file in bytes before rotation.

0
file_backup_count int

The number of backup log files to keep.

5
file_name Union[str, None]

The base name for the log file.

None
buffer_size int

The maximum number of log entries to buffer before writing to the CSV file.

1000
Source code in onshape_robotics_toolkit\log.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def __init__(
    self,
    log_path: str = "./",
    log_format: str = "[%(asctime)s] %(levelname)s: %(message)s",
    file_level: LogLevel = LogLevel.DEBUG,
    stream_level: LogLevel = LogLevel.INFO,
    file_max_bytes: int = 0,
    file_backup_count: int = 5,
    file_name: Union[str, None] = None,
    buffer_size: int = 1000,
) -> None:
    """
    Initialize the custom logger with the specified configuration.

    Args:
        log_path: The path to save log files.
        log_format: The log message format.
        file_level: The log level for file output.
        stream_level: The log level for console output.
        file_max_bytes: The maximum size of the log file in bytes before rotation.
        file_backup_count: The number of backup log files to keep.
        file_name: The base name for the log file.
        buffer_size: The maximum number of log entries to buffer before writing to the CSV file.
    """
    if not hasattr(self, "_initialized"):
        super().__init__(__name__)
        self._log_path = log_path
        self._log_format = log_format
        self._file_level = file_level
        self._stream_level = stream_level
        self._file_max_bytes = file_max_bytes
        self._file_backup_count = file_backup_count
        self._user_file_name = file_name

        self._file_path: str = ""
        self._csv_path: str = ""
        self._file: Optional[Any] = None
        self._writer = None
        self._is_logging = False
        self._header_written = False

        self._tracked_vars: dict[int, Callable[[], Any]] = {}
        self._var_names: dict[int, str] = {}
        self._buffer: deque[list[str]] = deque(maxlen=buffer_size)
        self._buffer_size: int = buffer_size

        self._setup_logging()
        self._initialized: bool = True
    else:
        self.set_file_name(file_name)
        self.set_file_level(file_level)
        self.set_stream_level(stream_level)
        self.set_format(log_format)
        self._file_max_bytes = file_max_bytes
        self._file_backup_count = file_backup_count
        self.set_buffer_size(buffer_size)

        self._log_path = log_path

close()

Close the logger and flush any remaining log entries.

Examples:

>>> LOGGER.close()
>>> LOGGER.info("This message will not be logged")
Source code in onshape_robotics_toolkit\log.py
415
416
417
418
419
420
421
422
423
424
425
426
427
def close(self) -> None:
    """
    Close the logger and flush any remaining log entries.

    Examples:
        >>> LOGGER.close()
        >>> LOGGER.info("This message will not be logged")
    """
    self.flush_buffer()
    if self._file:
        self._file.close()
        self._file = None
        self._writer = None

flush_buffer()

Write the buffered log entries to the CSV file.

Source code in onshape_robotics_toolkit\log.py
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
def flush_buffer(self) -> None:
    """
    Write the buffered log entries to the CSV file.
    """
    if not self._buffer:
        return

    self._ensure_file_handler()

    if self._file is None:
        self._file = open(self._csv_path, "w", newline="")
        self._writer = csv.writer(self._file)

    if not self._header_written:
        self._write_header()

    self._writer.writerows(self._buffer)
    self._buffer.clear()
    self._file.flush()

reset()

Reset the logger state.

Source code in onshape_robotics_toolkit\log.py
403
404
405
406
407
408
409
410
411
412
413
def reset(self) -> None:
    """
    Reset the logger state.
    """
    self._buffer.clear()
    self._tracked_vars.clear()
    self._var_names.clear()
    self._header_written = False
    if hasattr(self, "_file_handler"):
        self._file_handler.close()
        del self._file_handler

set_buffer_size(buffer_size)

Set the maximum number of log entries to buffer before writing to the CSV file.

Parameters:

Name Type Description Default
buffer_size int

The maximum number of log entries to buffer.

required
Source code in onshape_robotics_toolkit\log.py
325
326
327
328
329
330
331
332
333
def set_buffer_size(self, buffer_size: int) -> None:
    """
    Set the maximum number of log entries to buffer before writing to the CSV file.

    Args:
        buffer_size: The maximum number of log entries to buffer.
    """
    self._buffer_size = buffer_size
    self._buffer = deque(self._buffer, maxlen=buffer_size)

set_file_level(level)

Set the log level for file output.

Parameters:

Name Type Description Default
level LogLevel

The log level for file output.

required

Examples:

>>> LOGGER.set_file_level(LogLevel.INFO)
>>> LOGGER.file_level
LogLevel.INFO
>>> LOGGER.debug("This is a debug message and will not be logged")
Source code in onshape_robotics_toolkit\log.py
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
def set_file_level(self, level: LogLevel) -> None:
    """
    Set the log level for file output.

    Args:
        level: The log level for file output.

    Examples:
        >>> LOGGER.set_file_level(LogLevel.INFO)
        >>> LOGGER.file_level
        LogLevel.INFO
        >>> LOGGER.debug("This is a debug message and will not be logged")
    """
    self._file_level = level
    if hasattr(self, "_file_handler"):
        self._file_handler.setLevel(level=level.value)

set_file_name(file_name)

Set the base name for the log file.

Parameters:

Name Type Description Default
file_name Union[str, None]

The base name for the log file.

required

Examples:

>>> LOGGER.set_file_name("my_log_file")
>>> LOGGER.file_path
"./my_log_file.log"
Source code in onshape_robotics_toolkit\log.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
def set_file_name(self, file_name: Union[str, None]) -> None:
    """
    Set the base name for the log file.

    Args:
        file_name: The base name for the log file.

    Examples:
        >>> LOGGER.set_file_name("my_log_file")
        >>> LOGGER.file_path
        "./my_log_file.log"
    """
    # if filename has an extension, remove it
    if file_name is not None and "." in file_name:
        file_name = file_name.split(".")[0]

    self._user_file_name = file_name
    self._file_path = ""
    self._csv_path = ""

set_format(log_format)

Set the log message format. The format string uses the same syntax as the built-in Python logging module.

Parameters:

Name Type Description Default
log_format str

The log message format.

required

Examples:

>>> LOGGER.set_format("[%(asctime)s] %(levelname)s: %(message)s")
>>> LOGGER.info("This is an info message")
[2022-01-01 12:00:00] INFO: This is an info message
Source code in onshape_robotics_toolkit\log.py
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
def set_format(self, log_format: str) -> None:
    """
    Set the log message format. The format string uses the same syntax as the built-in Python logging module.

    Args:
        log_format: The log message format.

    Examples:
        >>> LOGGER.set_format("[%(asctime)s] %(levelname)s: %(message)s")
        >>> LOGGER.info("This is an info message")
        [2022-01-01 12:00:00] INFO: This is an info message
    """
    self._log_format = log_format
    self._std_formatter = logging.Formatter(log_format)
    if hasattr(self, "_file_handler"):
        self._file_handler.setFormatter(fmt=self._std_formatter)
    self._stream_handler.setFormatter(fmt=self._std_formatter)

set_stream_level(level)

Set the log level for console output.

Parameters:

Name Type Description Default
level LogLevel

The log level for console output.

required

Examples:

>>> LOGGER.set_stream_level(LogLevel.INFO)
>>> LOGGER.stream_level
LogLevel.INFO
>>> LOGGER.debug("This is a debug message and will not be streamed")
Source code in onshape_robotics_toolkit\log.py
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
def set_stream_level(self, level: LogLevel) -> None:
    """
    Set the log level for console output.

    Args:
        level: The log level for console output.

    Examples:
        >>> LOGGER.set_stream_level(LogLevel.INFO)
        >>> LOGGER.stream_level
        LogLevel.INFO
        >>> LOGGER.debug("This is a debug message and will not be streamed")
    """
    self._stream_level = level
    self._stream_handler.setLevel(level=level.value)

track_variable(var_func, name)

Record the value of a variable and log it to a CSV file.

Parameters:

Name Type Description Default
var_func Callable[[], Any]

A function that returns the value of the variable.

required
name str

The name of the variable.

required

Examples:

>>> class MyClass:
...     def __init__(self):
...         self.value = 42
>>> obj = MyClass()
>>> LOGGER.track_variable(lambda: obj.value, "answer")
>>> LOGGER.update()
>>> LOGGER.flush_buffer()
Source code in onshape_robotics_toolkit\log.py
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
def track_variable(self, var_func: Callable[[], Any], name: str) -> None:
    """
    Record the value of a variable and log it to a CSV file.

    Args:
        var_func: A function that returns the value of the variable.
        name: The name of the variable.

    Examples:
        >>> class MyClass:
        ...     def __init__(self):
        ...         self.value = 42
        >>> obj = MyClass()
        >>> LOGGER.track_variable(lambda: obj.value, "answer")
        >>> LOGGER.update()
        >>> LOGGER.flush_buffer()
    """

    var_id = id(var_func)
    self._tracked_vars[var_id] = var_func
    self._var_names[var_id] = name

untrack_variable(var_func)

Stop tracking a variable and remove it from the logger buffer.

Parameters:

Name Type Description Default
var_func Callable[[], Any]

The function used to track the variable.

required

Examples:

>>> class MyClass:
...     def __init__(self):
...         self.value = 42
>>> obj = MyClass()
>>> LOGGER.track_variable(lambda: obj.value, "answer")
>>> LOGGER.update()
>>> LOGGER.flush_buffer()
>>> LOGGER.untrack_variable(lambda: obj.value)
Source code in onshape_robotics_toolkit\log.py
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
def untrack_variable(self, var_func: Callable[[], Any]) -> None:
    """
    Stop tracking a variable and remove it from the logger buffer.

    Args:
        var_func: The function used to track the variable.

    Examples:
        >>> class MyClass:
        ...     def __init__(self):
        ...         self.value = 42
        >>> obj = MyClass()
        >>> LOGGER.track_variable(lambda: obj.value, "answer")
        >>> LOGGER.update()
        >>> LOGGER.flush_buffer()
        >>> LOGGER.untrack_variable(lambda: obj.value)
    """
    var_id = id(var_func)
    self._tracked_vars.pop(var_id, None)
    self._var_names.pop(var_id, None)

update()

Update the logger by logging the current values of tracked variables to the buffer.

Examples:

>>> class MyClass:
...     def __init__(self):
...         self.value = 42
>>> obj = MyClass()
>>> LOGGER.track_variable(lambda: obj.value, "answer")
>>> LOGGER.update()
Source code in onshape_robotics_toolkit\log.py
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
def update(self) -> None:
    """
    Update the logger by logging the current values of tracked variables to the buffer.

    Examples:
        >>> class MyClass:
        ...     def __init__(self):
        ...         self.value = 42
        >>> obj = MyClass()
        >>> LOGGER.track_variable(lambda: obj.value, "answer")
        >>> LOGGER.update()
    """
    if not self._tracked_vars:
        return

    data = []
    for _var_id, get_value in self._tracked_vars.items():
        value = get_value()
        data.append(str(value))

    self._buffer.append(data)

    if len(self._buffer) >= self._buffer_size:
        self.flush_buffer()