Great ideas! Thanks for your thoughts.

Try this version of weeutil/logger.py. It includes a shim that does the
proper type conversion before passing on arguments to
logging.handlers.RotatingFileHandler.

To use should be as simple as adding this to weewx.conf (alas, NOT TESTED):

[Logging]
  [[loggers]]
    [[[root]]]
      handlers = syslog, rotate
  [[handlers]]
    [[[rotate]]]
      filename = /tmp/weewx.log

By default, it logs to /var/log/weewx.log. Hence, the need to override that
option if you want to log to /tmp/weewx.log.

-tk


On Sat, May 2, 2020 at 8:05 AM Graham Eddy <graham.e...@gmail.com> wrote:

> i think we all agree that syslog on macos is a lost cause.
> the corollary is that pointing python logging at syslog (via
> logging.handlers.SysLogHandler) is doomed
>
> i accept that syslog works fine on most platforms, but it is not feasible
> for macos
> but there is the simple alternative for macos users of using a different
> handler:
>
> i patched log_dict in bin/weeutil/logger.py to add a
> logging.handlers.RotatingFileHandler and it worked fine.
> adding it via weewx.conf gives a type conversion error that seems simple
> to fix.
> with the fix, i would suggest this be added to the macos installation
> instructions for user to add to weewx.conf (or make it part of the
> installation process)
>
> details follow...
>
> weewx.conf (yes, using that ‘root’ hack introduced to work around a
> ConfigObj limitation):
>
> [Logging]
>   [[loggers]]
>     [[[root]]]
>       level = DEBUG
>       propogate = True
>       handlers = syslog, rotate
>   [[handlers]]
>     [[[rotate]]]
>       level = DEBUG
>       formatter = standard
>       class = logging.handlers.RotatingFileHandler
>       filename = /tmp/weewx.log
>       maxBytes = 10000000
>       backupCount = 2
>
>
> gives the following error, which on the face of it would take a simple fix
> of type conversions:
>
> Traceback (most recent call last):
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/config.py",
> line 563, in configure
>     handler = self.configure_handler(handlers[name])
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/config.py",
> line 736, in configure_handler
>     result = factory(**kwargs)
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/handlers.py",
> line 146, in __init__
>     if maxBytes > 0:
> TypeError: '>' not supported between instances of 'str' and ‘int’ < - - -
> SEE HER*E*
>
> The above exception was the direct cause of the following exception:
>
> Traceback (most recent call last):
>   File "bin/weewxd", line 261, in <module>
>     main()
>   File "bin/weewxd", line 136, in main
>     weeutil.logger.setup(options.log_label, config_dict)
>   File "/opt/weewx/bin/weeutil/logger.py", line 203, in setup
>     logging.config.dictConfig(log_dict)
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/config.py",
> line 800, in dictConfig
>     dictConfigClass(config).configure()
>   File
> "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/logging/config.py",
> line 571, in configure
>     '%r' % name) from e
> ValueError: Unable to configure handler 'rotate'
>
>
> the log_dict it produces before crashing seems correct:
>
> {'version': 1, 'disable_existing_loggers': False, 'loggers': {'':
> {'level': 'DEBUG', 'propagate': True, 'handlers': ['syslog'], 'propogate':
> 'True'}}, 'handlers': {'syslog': {'level': 'DEBUG', 'formatter':
> 'standard', 'class': 'logging.handlers.SysLogHandler', 'address':
> '/var/run/syslog', 'facility': 'local1'}, 'console': {'level': 'DEBUG',
> 'formatter': 'verbose', 'class': 'logging.StreamHandler', 'stream': '
> ext://sys.stdout'}, 'rotate': {'level': 'DEBUG', 'formatter': 'standard',
> 'class': 'logging.handlers.RotatingFileHandler', 'filename':
> '/tmp/weewx.log', 'maxBytes': '10000000', 'backupCount': '2'}},
> 'formatters': {'simple': {'format': '%(levelname)s %(message)s'},
> 'standard': {'format': 'weewx[%(process)d] %(levelname)s %(name)s:
> %(message)s'}, 'verbose': {'format': '%(asctime)s  weewx[%(process)d]
> %(levelname)s %(name)s: %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S'}}}
>
>
> cheers
>
> On 2 May 2020, at 10:42 pm, Tom Keffer <tkef...@gmail.com> wrote:
>
> Logging from Python under MacOS High Sierra and later is a complete
> mystery. I have been unable to get it to work and, it seems, neither have
> other
> <https://apple.stackexchange.com/questions/256769/how-to-use-logger-command-on-sierra>
>  users
> <https://stackoverflow.com/questions/49805750/macos-high-sierra-syslog-does-not-work/51052538>
> .
>
> Wish I could be of more help...
>
> -tk
>
> On Sat, May 2, 2020 at 5:26 AM Graham Eddy <graham.e...@gmail.com> wrote:
>
>> i reported this incorrectly. instead of giveing me an unchanged logger
>> format, it gives me no lines at all (from the point the weewx.conf log
>> format is parsed).  even the following does the same (no logger output).
>> commenting out the “format” line reverts to lines being produced, in
>> original format of course
>>
>> [Logging]
>>   [[formatters]]
>>     [[[standard]]]
>>       format = '%(message)s'
>>
>
> --
> You received this message because you are subscribed to the Google Groups
> "weewx-user" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to weewx-user+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/weewx-user/85C23BE1-912C-4EAA-80AC-F7E81F8EE021%40gmail.com
> <https://groups.google.com/d/msgid/weewx-user/85C23BE1-912C-4EAA-80AC-F7E81F8EE021%40gmail.com?utm_medium=email&utm_source=footer>
> .
>

-- 
You received this message because you are subscribed to the Google Groups 
"weewx-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to weewx-user+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/weewx-user/CAPq0zEACT7zvUHsqwj0TODGn2fWqC_Yd6QYSifZcXuOYQmWXYQ%40mail.gmail.com.
#
#    Copyright (c) 2019 Tom Keffer <tkef...@gmail.com>
#
#    See the file LICENSE.txt for your full rights.
#
"""WeeWX logging facility"""

from __future__ import absolute_import

import sys
import logging.config
from six.moves import StringIO

import configobj

import weewx
from weeutil.weeutil import to_int, to_bool

# These values are known only at runtime
if sys.platform == "darwin":
    address = '/var/run/syslog'
    facility = 'local1'
elif sys.platform.startswith('linux'):
    address = '/dev/log'
    facility = 'user'
elif sys.platform.startswith('freebsd'):
    address = '/var/run/log'
    facility = 'user'
elif sys.platform.startswith('netbsd'):
    address = '/var/run/log'
    facility = 'user'
elif sys.platform.startswith('openbsd'):
    address = '/dev/log'
    facility = 'user'
else:
    address = ('localhost', 514)
    facility = 'user'

# The logging defaults. Note that two kinds of placeholders are used:
#
#  {value}: these are plugged in by the function setup().
#  %(value)s: these are plugged in by the Python logging module.
#
LOGGING_STR = """[Logging]
    version = 1
    disable_existing_loggers = False
      
    [[loggers]]
        # Root logger
        [[[root]]]
          level = {log_level}
          propagate = 1
          handlers = syslog,
    
    # Definitions of possible logging destinations
    [[handlers]]
    
        # System logger
        [[[syslog]]]
            level = DEBUG
            formatter = standard
            class = logging.handlers.SysLogHandler
            address = {address}
            facility = {facility}
    
        # Log to console
        [[[console]]]
            level = DEBUG
            formatter = verbose
            class = logging.StreamHandler
            # Alternate choice is 'ext://sys.stderr'
            stream = ext://sys.stdout

        # Log to a set of rotating files    
        [[[rotate]]]
            level = DEBUG
            formatter = standard
            class = weeutil.logger.RotatingFileHandler
            filename = /var/log/weewx.log
            maxBytes = 10000000
            backupCount = 4

    # How to format log messages
    [[formatters]]
        [[[simple]]]
            format = "%(levelname)s %(message)s"
        [[[standard]]]
            format = "{process_name}[%(process)d] %(levelname)s %(name)s: %(message)s" 
        [[[verbose]]]
            format = "%(asctime)s  {process_name}[%(process)d] %(levelname)s %(name)s: %(message)s"
            # Format to use for dates and times:
            datefmt = %Y-%m-%d %H:%M:%S
"""


def setup(process_name, user_log_dict):
    """Set up the weewx logging facility"""

    # Create a ConfigObj from the default string. No interpolation (it interferes with the
    # interpolation directives embedded in the string).
    log_config = configobj.ConfigObj(StringIO(LOGGING_STR), interpolation=False, encoding='utf-8')

    # Turn off interpolation in the incoming dictionary. First save the old
    # value, then restore later. However, the incoming dictionary may be a simple
    # Python dictionary and not have interpolation. Hence the try block.
    try:
        old_interpolation = user_log_dict.interpolation
        user_log_dict.interpolation = False
    except AttributeError:
        old_interpolation = None

    # Merge in the user additions / changes:
    log_config.merge(user_log_dict)

    # Restore the old interpolation value
    if old_interpolation is not None:
        user_log_dict.interpolation = old_interpolation

    # Adjust the logging level in accordance with whether or not the 'debug' flag is on
    log_level = 'DEBUG' if weewx.debug else 'INFO'

    # Now we need to walk the structure, plugging in the values we know.
    # First, we need a function to do this:
    def _fix(section, key):
        if isinstance(section[key], (list, tuple)):
            # The value is a list or tuple
            section[key] = [item.format(log_level=log_level,
                                        address=address,
                                        facility=facility,
                                        process_name=process_name) for item in section[key]]
        else:
            # The value is a string
            section[key] = section[key].format(log_level=log_level,
                                               address=address,
                                               facility=facility,
                                               process_name=process_name)

    # Using the function, walk the 'Logging' part of the structure
    log_config['Logging'].walk(_fix)

    # Extract just the part used by Python's logging facility
    log_dict = log_config.dict().get('Logging', {})

    # The root logger is denoted by an empty string by the logging facility. Unfortunately,
    # ConfigObj does not accept an empty string as a key. So, instead, we use this hack:
    try:
        log_dict['loggers'][''] = log_dict['loggers']['root']
        del log_dict['loggers']['root']
    except KeyError:
        pass

    # Make sure values are of the right type
    if 'version' in log_dict:
        log_dict['version'] = to_int(log_dict['version'])
    if 'disable_existing_loggers' in log_dict:
        log_dict['disable_existing_loggers'] = to_bool(log_dict['disable_existing_loggers'])
    if 'loggers' in log_dict:
        for logger in log_dict['loggers']:
            if 'propagate' in log_dict['loggers'][logger]:
                log_dict['loggers'][logger]['propagate'] = to_bool(log_dict['loggers'][logger]['propagate'])

    # Finally! The dictionary is ready. Set the defaults.
    logging.config.dictConfig(log_dict)


def log_traceback(log_fn, prefix=''):
    """Log the stack traceback into a logger.

    log_fn: One of the logging.Logger logging functions, such as logging.Logger.warning.

    prefix: A string, which will be put in front of each log entry. Default is no string.
    """
    import traceback
    sfd = StringIO()
    traceback.print_exc(file=sfd)
    sfd.seek(0)
    for line in sfd:
        log_fn("%s%s", prefix, line)


class RotatingFileHandler(logging.handlers.RotatingFileHandler):
    """Shim for logging.handlers.RotatingFileHandler that does type conversions before passing on
    to superclass."""
    def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False):
        "Initializer. Perform the necessary type conversions."
        super(RotatingFileHandler, self).__init__(filename,
                                                  mode=mode,
                                                  maxBytes=to_int(maxBytes),
                                                  backupCount=to_int(backupCount),
                                                  encoding=encoding,
                                                  delay=to_bool(delay))

Reply via email to