0

I've created a python script that uses parallel-ssh module (AKA pssh) in order to run various commands on remote nodes. One of the commands is puppet agent -t, which outputs using ANSII color codes. When the script is running and outputs to the terminal, the coloring works as expected. However, in the script log I see that it doesn't parse the ANSII codes and instead I get an ugly wrapper for each line, as below:

2016-01-12 20:23:30,748  INFO: [ubuntu01]       ESC[1;31mWarning: Setting templatedir is deprecated. See http://links.puppetlabs.com/env-settings-deprecations
2016-01-12 20:23:30,748  INFO: [ubuntu01]       (at /usr/lib/ruby/vendor_ruby/puppet/settings.rb:1139:in `issue_deprecation_warning')ESC[0m
2016-01-12 20:23:30,749  INFO: [ubuntu01]       ESC[0;32mInfo: Retrieving pluginESC[0m
2016-01-12 20:23:31,984  INFO: [ubuntu01]       ESC[0;32mInfo: Caching catalog for ubuntu01.puppetlabESC[0m
2016-01-12 20:23:32,014  INFO: [ubuntu01]       ESC[0;32mInfo: Applying configuration version '1452623010'ESC[0m
2016-01-12 20:23:32,083  INFO: [ubuntu01]       ESC[mNotice: Finished catalog run in 0.08 secondsESC[0m
2016-01-12 20:23:32,351  INFO: [ubuntu01]       * agent is running
2016-01-12 20:23:32,353  INFO: [centos01]       ESC[0;32mInfo: Retrieving pluginfactsESC[0m
2016-01-12 20:23:32,353  INFO: [centos01]       ESC[0;32mInfo: Retrieving pluginESC[0m
2016-01-12 20:23:33,712  INFO: [centos01]       ESC[0;32mInfo: Caching catalog for centos01.puppetlabESC[0m
2016-01-12 20:23:33,838  INFO: [centos01]       ESC[0;32mInfo: Applying configuration version '1452623010'ESC[0m
2016-01-12 20:23:34,101  INFO: [centos01]       ESC[mNotice: Finished catalog run in 0.27 secondsESC[0m
2016-01-12 20:23:34,421  INFO: [centos01]       puppet (pid  2069) is running...

This is very frustrating, since it makes the log less readable. I've tried to modify the logger config with re.compile(r'\x1b[^m]*m') method which I've found in this thread, like this:

import logging
import re

ansi_escape = re.compile(r'\x1b[^m]*m')
message = ansi_escape.sub('', '%(message)s')


def set_logger(log_file):
    """Define the logger.."""
    try:
        logging.basicConfig(filename=log_file, level=logging.INFO,
                        format='%(asctime)s  %(levelname)s: ' + message)
        logger = logging.getLogger(__name__)
        return logger
    except IOError:
        print "ERROR: No permissions to access the log file! Please run the script as root user (sudo will also do the trick)..\n"
        exit(2)

The script runs properly, however no changes and the log still looks messy with all these ANSII codes. I assume that there might be another place in which I can set a separate handler for pssh logger, but I wasn't able to find it.

Any help would be very much appreciated!

Community
  • 1
  • 1
Moshe Vayner
  • 738
  • 1
  • 8
  • 23
  • Your current code replaces the logging template, not the actual message. The easiest way to solve this issue would be to clean the messages being logged before they get logged. – Thtu Jan 12 '16 at 19:04
  • Could you give a bit more details? The pssh module has it's own logger AFAIK, so I'm not sure I'm able to manipulate the output before it's being logged. Thanks! – Moshe Vayner Jan 12 '16 at 19:06

3 Answers3

2

Did you try to disable puppet colors?

puppet agent -t --color=false
MaxU - stand with Ukraine
  • 205,989
  • 36
  • 386
  • 419
  • Thanks! That's another way of working around this and could provide the solution. However, I do like the fact that the stdout is printed in colors, so would be great if there's a way to keep the coloring in the terminal and just ignore it when logging the output. If no such option will be found, I guess I'll go with your suggestion :-) – Moshe Vayner Jan 12 '16 at 19:08
  • Could you post pieces of code where you call "ParallelSSHClient" and where you use the output? – MaxU - stand with Ukraine Jan 12 '16 at 19:25
  • client = pssh.ParallelSSHClient(nodes, pool_size=args.batch, timeout=10, num_retries=1) output = client.run_command(command, sudo=True) for node in output: for line in output[node]['stdout']: print '[{0}] {1}'.format(node, line) That's the entire block. The pssh module knows how to pull the host logger and uses it. – Moshe Vayner Jan 12 '16 at 19:26
  • Just FYI- I've added --color=false to my puppet command and it indeeds gets the job done! So for now I'm going to use this, unless someone here will be able to solve the logging issue. Thanks a lot! I'll mark your post as a possible answer either way :-) – Moshe Vayner Jan 12 '16 at 19:42
0

Your current code only escapes the actual logging template. Hopefully someone can come along and tell you how to properly do this, but you could use a logging adapter, which is like a logger except it allows you to modify the message being logged.

class myAdapter(logging.LoggerAdapter):

    def process(self, msg, kwargs):
        msg = re.sub(r'\x1b[^m]*m', '', msg)
        return '%s' % (msg), kwargs

def set_logger(log_file):
    """Define the logger.."""
    try:
        logging.basicConfig(filename=log_file, level=logging.INFO,
                    format='%(asctime)s  %(levelname)s: ' + message)
        logger = logging.getLogger(__name__)
        return myAdapter(logger, {})
    except IOError:
        print "ERROR: No permissions to access the log file! Please run the script as root user (sudo will also do the trick)..\n"
        exit(2)

More information can be found in the logging cookbook : https://docs.python.org/2/howto/logging-cookbook.html

Thtu
  • 1,992
  • 15
  • 21
  • I've pasted the code you provided, but getting an error when calling the set_logger method: return MyAdapter(logger) TypeError: __init__() takes exactly 3 arguments (2 given) I've tried to understand which arguments exactly is __init__ looking for, but couldn't find it.. Care to shed more light? :-) Thanks! – Moshe Vayner Jan 12 '16 at 19:30
0

This version keeps the original output and adds 'stdout.noansi' so you'll have both the original output and the one without ANSI formatting

import re

client = pssh.ParallelSSHClient(nodes, pool_size=args.batch, timeout=10, num_retries=1)

output = client.run_command(command, sudo=True)

for node in output:
    # let's remove ansi formatting and put it into 'stdout.noansi'...
    output[node]['stdout.noansi'] = re.sub(r'\x1b\[[^m]*?m', '', output[node]['stdout'], re.I | re.S | re.M)
    print '--------------- <<<<< NOANSI OUTPUT >>>>> ---------------'
    [print '[{0}] {1}'.format(node, line) for line in output[node]['stdout.noansi'] ]
    print '--------------- <<<<< ORIGINAL OUTPUT >>>>> ---------------'
    [print '[{0}] {1}'.format(node, line) for line in output[node]['stdout'] ]
MaxU - stand with Ukraine
  • 205,989
  • 36
  • 386
  • 419
  • Hey @MaxU Thanks, I already tried that, but unfortunately it only changes the output to stdout. The log remains the same even with this modification. – Moshe Vayner Jan 14 '16 at 09:01
  • You can remove ANSI formatting from the whole text block and have both versions: an original output and NOANSI output. I have updated code in the answer. – MaxU - stand with Ukraine Jan 14 '16 at 18:32