My previous answer to this question is not as good as I thought (https://stackoverflow.com/a/61087617/3167448). I think the real answer to this question is to simply use a logger. I did not know what loggers were until recently but they are much better.
It's much better to create a logger object that sends your strings to a log file AND to the stdout. It even allows you to route the messages more finely according to the threshold level. Here is the code:
def logger_SO_print_and_write_to_my_stdout():
"""My sample logger code to print to screen and write to file (the same thing).
Note: trying to replace this old answer of mine using a logger:
- https://github.com/CoreyMSchafer/code_snippets/tree/master/Logging-Advanced
Credit:
- https://www.youtube.com/watch?v=jxmzY9soFXg&t=468s
- https://github.com/CoreyMSchafer/code_snippets/tree/master/Logging-Advanced
- https://stackoverflow.com/questions/21494468/about-notset-in-python-logging/21494716#21494716
Other resources:
- https://docs.python-guide.org/writing/logging/
- https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
"""
from pathlib import Path
import logging
import os
import sys
from datetime import datetime
## create directory (& its parents) if it does not exist otherwise do nothing :)
# get current time
current_time = datetime.now().strftime('%b%d_%H-%M-%S')
logs_dirpath = Path(f'~/logs/python_playground_logs_{current_time}/').expanduser()
logs_dirpath.mkdir(parents=True, exist_ok=True)
my_stdout_filename = logs_dirpath / Path('my_stdout.log')
# remove my_stdout if it exists (note you can also just create a new log dir/file each time or append to the end of the log file your using)
#os.remove(my_stdout_filename) if os.path.isfile(my_stdout_filename) else None
## create top logger
logger = logging.getLogger(__name__) # loggers are created in hierarchy using dot notation, thus __name__ ensures no name collisions.
logger.setLevel(logging.DEBUG) # note: use logging.DEBUG, CAREFUL with logging.UNSET: https://stackoverflow.com/questions/21494468/about-notset-in-python-logging/21494716#21494716
## log to my_stdout.log file
file_handler = logging.FileHandler(filename=my_stdout_filename)
#file_handler.setLevel(logging.INFO) # not setting it means it inherits the logger. It will log everything from DEBUG upwards in severity to this handler.
log_format = "{asctime}:{levelname}:{lineno}:{name}:{message}" # see for logrecord attributes https://docs.python.org/3/library/logging.html#logrecord-attributes
formatter = logging.Formatter(fmt=log_format, style='{') # set the logging format at for this handler
file_handler.setFormatter(fmt=formatter)
## log to stdout/screen
stdout_stream_handler = logging.StreamHandler(stream=sys.stdout) # default stderr, though not sure the advatages of logging to one or the other
#stdout_stream_handler.setLevel(logging.INFO) # Note: having different set levels means that we can route using a threshold what gets logged to this handler
log_format = "{name}:{levelname}:-> {message}" # see for logrecord attributes https://docs.python.org/3/library/logging.html#logrecord-attributes
formatter = logging.Formatter(fmt=log_format, style='{') # set the logging format at for this handler
stdout_stream_handler.setFormatter(fmt=formatter)
logger.addHandler(hdlr=file_handler) # add this file handler to top logger
logger.addHandler(hdlr=stdout_stream_handler) # add this file handler to top logger
logger.log(logging.NOTSET, 'notset')
logger.debug('debug')
logger.info('info')
logger.warning('warning')
logger.error('error')
logger.critical('critical')
log content:
2020-04-16 11:28:24,987:DEBUG:154:__main__:debug
2020-04-16 11:28:24,988:INFO:155:__main__:info
2020-04-16 11:28:24,988:WARNING:156:__main__:warning
2020-04-16 11:28:24,988:ERROR:157:__main__:error
2020-04-16 11:28:24,988:CRITICAL:158:__main__:critical
terminal stdout output:
__main__:DEBUG:-> debug
__main__:INFO:-> info
__main__:WARNING:-> warning
__main__:ERROR:-> error
__main__:CRITICAL:-> critical
I feel this is a particular important question/answer to reference just in case you have issues with UNSET
: About NOTSET in python logging thank god for the answer and question there.