Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log Capturing for devs wishing to capture notification output #311

Merged
merged 5 commits into from
Oct 13, 2020

Conversation

caronc
Copy link
Owner

@caronc caronc commented Oct 9, 2020

Description:

Related issue (if applicable): #306

  • LogCapture() class created for storing log output in memory.

LogCapture works as follows:

import apprise

# instantiate our notification services:
apobj = apprise.Apprise()

# Add all the notifications we want:
apobj.add('discord://credentials')
apobj.add('kodi://my.kodi.server')
apobj.add('json://localhost')

# but instead of calling apobj.notify() like one would normally do; capture the
# response from it:
with apprise.LogCapture(level=apprise.logging.INFO) as logs:
     # now call our notification
     apobj.notify("hello world")

     # At this point whether we were successful or not, we have all of the logs
     # generated from the above command stored in logs (which is of type StringIO)

     print(logs.getvalue())
     # Might print something like so:
     # 2020-10-09 16:14:10,284 - INFO - An 'info' message here
     # 2020-10-09 16:14:10,284 - WARNING - An 'warning' message here
     # 2020-10-09 16:14:10,284 - ERROR - An 'error' message here

You can perform all kinds of manipulation on the logs at this point such as converting it all to HTML for display:

import re
with apprise.LogCapture(level=apprise.logging.INFO) as logs:
     # now call our notification
     apobj.notify("hello world")

     # convert them into a list if you like:
     entries_as_list = logs.rstrip().split('\n')

    # This regular expression might work great for parsing for you:
    log_entry_re = re.compile(
        r'(?P<datetime>[^,]+),0*?(?P<ms>[0-9]+) - '
        r'(?P<type>[a-z ]+) - (?P<message>.*)', re.I)

    # Iterate over each entry and access them
    for entry in entries_as_list:
        match = log_entry_re.match(entry)
        if match:
            print(match.group('datetime'))
            print(match.group('ms'))
            print(match.group('type'))
            print(match.group('message'))
        else:
            # the line is a carry over from the previous line (hence, it was
            # a multi-lined log entry).
            # Treat the entire thing as still part of the 'message'; it's
            # content should be associated with the previous line
            print(entry)
    # This is the same as the above, except we prepare some HTML formatting:
    bullets = []
    for entry in entries_as_list:
        match = log_entry_re.match(entry)
        if match:
            if bullets:
                # append our tail
                bullets[-1] += '</span></li>'

            bullets.append(
                '<li><span class="log_date">{datetime},{ms}</span> - '
                '<span class="log_type">{type}</span> - '
                '<span class="log_msg">{message}'.format(
                    datetime=match.group('datetime'),
                    ms=match.group('ms'),
                    type=match.group('type'),
                    message=match.group('message'),
            ))

        else:   # append our content to the end of our last message
            if bullets:
                # append our tail
                bullets[-1] += '<br/>' + entry

    # after our for-loop add our tail
    if bullets:
        # append our tail
        bullets[-1] += '</span></li>'

    # Now wrap our content in an <ul> block:
    html = '<ul class="log_entries">{}</ul>.format(''.join(bullets))

If you want to have your capture get written to a file instead of a temporary memory object, just specify a path to the filename you wish to write the content to:

with apprise.LogCapture(path="/tmp/capture", level=apprise.logging.INFO) as fp:
     # now call our notification
     apobj.notify("hello world")

     # Because `path` was used here, we have access to a file pointer to the log (not a StringIO object).
     # You can easily access the logs like so
     content = fp.read()

Checklist

  • The code change is tested and works locally.
  • There is no commented out code in this PR.
  • No lint errors (use flake8)
  • 100% test coverage

@codecov-io
Copy link

codecov-io commented Oct 9, 2020

Codecov Report

Merging #311 into master will not change coverage.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff            @@
##            master      #311   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           84        84           
  Lines        10489     10533   +44     
  Branches      1752      1759    +7     
=========================================
+ Hits         10489     10533   +44     
Impacted Files Coverage Δ
apprise/URLBase.py 100.00% <100.00%> (ø)
apprise/__init__.py 100.00% <100.00%> (ø)
apprise/logger.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 53a8d04...51dbf62. Read the comment docs.

@caronc caronc merged commit 52a8317 into master Oct 13, 2020
@caronc caronc deleted the 306-notify-response-object branch October 13, 2020 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants