# Copyright (c) 2015-2023 Patricio Cubillos and contributors.
# mc3 is open-source software under the MIT license (see LICENSE).
__all__ = [
'Log',
]
import sys
import time
import textwrap
import numpy as np
[docs]class Log():
"""
Dual file/stdout logging class with conditional printing.
"""
def __init__(self, logname=None, verb=2, append=False, width=70):
"""
Parameters
----------
logname: String
Name of FILE pointer where to store log entries. Set to None to
print only to stdout.
verb: Integer
Conditional threshold to print messages. There are five levels
of increasing verbosity:
verb < 0: only print error() calls.
verb >= 0: print warning() calls.
verb >= 1: print head() calls.
verb >= 2: print msg() calls.
verb >= 3: print debug() calls.
append: Bool
If True, append logged text to existing file.
If False, write logs to new file.
width: Integer
Maximum length of each line of text (longer texts will be break
down into multiple lines).
"""
self.logname = logname
if self.logname is not None:
if append:
self.file = open(self.logname, 'a')
else:
self.file = open(self.logname, 'w')
else:
self.file = None
self.verb = verb
self.indent = 0
self.width = width
self.warnings = []
self.sep = 70*":" # Warning separator
def __enter__(self):
return self
def __exit__(self, type, value, exception):
self.close()
[docs] def write(self, text):
"""
Write and flush text to stdout and FILE pointer if it exists.
Parameters
----------
text: String
Text to write.
"""
# Print to screen and file:
print(text)
sys.stdout.flush()
# Print to file, if requested:
if self.file is not None and not self.file.closed:
self.file.write(text + "\n")
self.file.flush()
[docs] def wrap(self, message, indent=None, si=None, width=None):
"""
Wrap text according to given/default indentation and width.
Parameters
----------
message: String
String to be printed.
indent: Integer
Number of blank spaces to indent the printed message.
si: Integer
Sub-sequent-lines indentation.
width: Integer
If not None, override text width (only for this specific call).
Returns
-------
text: String
Formatted output string.
"""
if indent is None:
indent = self.indent
if si is None:
si = self.indent
if width is None:
width = self.width
# Indentation texts:
indspace = " "*indent
sind = " "*si
# Break down the input text into the different sentences (line-breaks):
msg = [
textwrap.fill(
sentence,
break_long_words=False,
break_on_hyphens=False,
initial_indent=indspace,
subsequent_indent=sind,
width=width,
)
for sentence in message.splitlines()
]
return "\n".join(msg)
[docs] def debug(self, message, indent=None, si=None, width=None):
"""
Print wrapped message to screen and file if verbosity is > 2.
Parameters
----------
message: String
String to be printed.
indent: Integer
Number of blank spaces to indent the printed message.
si: Integer
Sub-sequent-lines indentation.
width: Integer
If not None, override text width (only for this specific call).
"""
if self.verb < 3:
return
text = self.wrap(message, indent, si, width)
self.write(text)
[docs] def msg(self, message, indent=None, si=None, width=None):
"""
Print wrapped message to screen and file if verbosity is > 1.
Parameters
----------
message: String
String to be printed.
indent: Integer
Number of blank spaces to indent the printed message.
si: Integer
Sub-sequent-lines indentation.
width: Integer
If not None, override text width (only for this specific call).
"""
if self.verb < 2:
return
text = self.wrap(message, indent, si, width)
self.write(text)
[docs] def head(self, message, indent=None, si=None, width=None):
"""
Print wrapped message to screen and file if verbosity is > 0.
Parameters
----------
message: String
String to be printed.
indent: Integer
Number of blank spaces to indent the printed message.
si: Integer
Sub-sequent-lines indentation.
width: Integer
If not None, override text width (only for this specific call).
"""
if self.verb < 1:
return
text = self.wrap(message, indent, si, width)
self.write(text)
[docs] def warning(self, message):
"""
Print a warning message surrounded by colon bands.
Parameters
----------
message: String
String to be printed.
"""
if self.verb < 0:
return
# Format the sub-text message:
subtext = self.wrap(message, indent=4)
# Add the warning surroundings:
text = (
f"\n{self.sep}"
"\n Warning:"
f"\n{subtext}"
f"\n{self.sep}\n"
)
# Store warnings:
self.warnings.append(subtext)
# Print to screen and file:
self.write(text)
[docs] def error(self, error_message, exception=ValueError, tracklev=None):
"""
Print error message to file and end the code execution.
Parameters
----------
message: String
String to be printed.
exception: Exception
The type of exception to be raised.
tracklev: --
Deprecated argument, kept for backward compatibility.
"""
# Generate string to print:
wrapped_text = self.wrap(error_message, indent=0)
# Print to file, if needed before raising the exception:
if self.file is not None and not self.file.closed:
self.file.write(f"\n{self.sep}\n{wrapped_text}\n{self.sep}")
self.close()
raise exception(error_message)
[docs] def progressbar(self, frac):
"""
Print out to screen [and file] a progress bar, percentage,
and current time.
Parameters
----------
frac: Float
Fraction of the task that has been completed, ranging from
0.0 (none) to 1.0 (completed).
"""
if self.verb < 1:
return
barlen = int(np.clip(round(10*frac), 0, 10))
bar = ":"*barlen + " "*(10-barlen)
text = f"\n[{bar}] {100*frac:5.1f}% completed ({time.ctime()})"
# Print to screen and to file:
self.write(text)
[docs] def close(self):
"""
Close log FILE pointer.
"""
if self.file is not None:
self.file.close()