diff --git a/ee/cli/plugins/site.py b/ee/cli/plugins/site.py index 91b00827..03bd14c8 100644 --- a/ee/cli/plugins/site.py +++ b/ee/cli/plugins/site.py @@ -106,10 +106,10 @@ class EESiteController(CementBaseController): @expose(help="Monitor example.com logs") def log(self): (ee_domain, ee_www_domain) = ValidateDomain(self.app.pargs.site_name) + ee_site_webroot = EEVariables.ee_webroot + ee_domain if os.path.isfile('/etc/nginx/sites-available/{0}' .format(ee_domain)): - EEShellExec.cmd_exec(self, 'tail -f /var/log/nginx/{0}.*.log' - .format(ee_domain)) + logwatch(self, ee_site_webroot + '/logs/') else: Log.error(self, " site {0} does not exists".format(ee_domain)) @@ -457,6 +457,8 @@ class EESiteCreateController(CementBaseController): " {0}".format(ee_wp_creds['wp_user'])) Log.info(self, Log.ENDC + "WordPress admin user password : {0}" .format(ee_wp_creds['wp_pass'])) + + display_cache_settings(self, data) addNewSite(self, ee_www_domain, stype, cache, ee_site_webroot) Log.info(self, "Successfully created site" " http://{0}".format(ee_domain)) @@ -900,7 +902,7 @@ class EESiteUpdateController(CementBaseController): " {0}".format(ee_wp_creds['wp_user'])) Log.info(self, Log.ENDC + "WordPress admin password : {0}" .format(ee_wp_creds['wp_pass']) + "\n\n") - + display_cache_settings(self, data) updateSiteInfo(self, ee_www_domain, stype=stype, cache=cache) Log.info(self, "Successfully updated site" " http://{0}".format(ee_domain)) @@ -916,7 +918,7 @@ class EESiteDeleteController(CementBaseController): (['site_name'], dict(help='domain name to be deleted')), (['--no-prompt'], - dict(help="dont ask for permission for delete", + dict(help="doesnt ask permission for delete", action='store_true')), (['--all'], dict(help="delete all", action='store_true')), diff --git a/ee/cli/plugins/site_functions.py b/ee/cli/plugins/site_functions.py index 92780c92..b0a47ac9 100644 --- a/ee/cli/plugins/site_functions.py +++ b/ee/cli/plugins/site_functions.py @@ -433,3 +433,69 @@ def updatewpuserpassword(self, ee_domain, ee_site_webroot): else: Log.error(self, "Invalid WordPress user {0} for {1}." .format(ee_wp_user, ee_domain)) + + +def display_cache_settings(self, data): + if data['wpsc']: + if data['multisite']: + Log.info(self, "Configure WPSC:" + "\t\thttp://{0}/wp-admin/network/settings.php?" + "page=wpsupercache" + .format(data['site_name'])) + else: + Log.info(self, "Configure WPSC:" + "\t\thttp://{0}/wp-admin/options-general.php?" + "page=wpsupercache" + .format(data['site_name'])) + + if data['wpfc']: + if data['multisite']: + Log.info(self, "Configure nginx-helper:" + "\thttp://{0}/wp-admin/network/settings.php?" + "page=nginx".format(data['site_name'])) + else: + Log.info(self, "Configure nginx-helper:" + "\thttp://{0}/wp-admin/options-general.php?" + "page=nginx".format(data['site_name'])) + + if data['wpfc'] or data['w3tc']: + if data['multisite']: + Log.info(self, "Configure W3TC:" + "\t\thttp://{0}/wp-admin/network/admin.php?" + "page=w3tc_general".format(data['site_name'])) + else: + Log.info(self, "Configure W3TC:" + "\t\thttp://{0}wp-admin/admin.php?" + "page=w3tc_general".format(data['site_name'])) + + if data['wpfc']: + Log.info(self, "Page Cache:\t\tDisable") + elif data['w3tc']: + Log.info(self, "Page Cache:\t\tDisk Enhanced") + Log.info(self, "Database Cache:\t\tMemcached") + Log.info(self, "Object Cache:\t\tMemcached") + Log.info(self, "Browser Cache:\t\tDisable") + + +def logwatch(self, logdir): + import zlib + import base64 + import time + from ee.core import logwatch + + def callback(filename, lines): + for line in lines: + if line.find(':::') == -1: + print(line) + else: + data = line.split(':::') + try: + print(data[0], data[1], + zlib.decompress(base64.decodestring(data[2]))) + except Exception as e: + Log.info(time.time(), + 'caught exception rendering a new log line in %s' + % filename) + + l = logwatch.LogWatcher(logdir, callback) + l.loop() diff --git a/ee/core/logwatch.py b/ee/core/logwatch.py new file mode 100644 index 00000000..649d418f --- /dev/null +++ b/ee/core/logwatch.py @@ -0,0 +1,192 @@ + +""" +Real time log files watcher supporting log rotation. +""" + +import os +import time +import errno +import stat +from ee.core.logging import Log + + +class LogWatcher(object): + """Looks for changes in all files of a directory. + This is useful for watching log file changes in real-time. + It also supports files rotation. + + Example: + + >>> def callback(filename, lines): + ... print filename, lines + ... + >>> l = LogWatcher("/var/www/example.com/logs", callback) + >>> l.loop() + """ + + def __init__(self, folder, callback, extensions=["log"], tail_lines=0): + """Arguments: + + (str) @folder: + the folder to watch + + (callable) @callback: + a function which is called every time a new line in a + file being watched is found; + this is called with "filename" and "lines" arguments. + + (list) @extensions: + only watch files with these extensions + + (int) @tail_lines: + read last N lines from files being watched before starting + """ + self.files_map = {} + self.callback = callback + self.folder = os.path.realpath(folder) + self.extensions = extensions + assert (os.path.isdir(self.folder), "%s does not exists" + % self.folder) + assert callable(callback) + self.update_files() + # The first time we run the script we move all file markers at EOF. + # In case of files created afterwards we don't do this. + for id, file in list(iter(self.files_map.items())): + file.seek(os.path.getsize(file.name)) # EOF + if tail_lines: + lines = self.tail(file.name, tail_lines) + if lines: + self.callback(file.name, lines) + + def __del__(self): + self.close() + + def loop(self, interval=0.1, async=False): + """Start the loop. + If async is True make one loop then return. + """ + while 1: + self.update_files() + for fid, file in list(iter(self.files_map.items())): + self.readfile(file) + if async: + return + time.sleep(interval) + + def log(self, line): + """Log when a file is un/watched""" + print(line) + + def listdir(self): + """List directory and filter files by extension. + You may want to override this to add extra logic or + globbling support. + """ + ls = os.listdir(self.folder) + if self.extensions: + return ([x for x in ls if os.path.splitext(x)[1][1:] + in self.extensions]) + else: + return ls + + @staticmethod + def tail(fname, window): + """Read last N lines from file fname.""" + try: + f = open(fname, 'r') + except IOError as err: + if err.errno == errno.ENOENT: + return [] + else: + raise + else: + BUFSIZ = 1024 + f.seek(0, os.SEEK_END) + fsize = f.tell() + block = -1 + data = "" + exit = False + while not exit: + step = (block * BUFSIZ) + if abs(step) >= fsize: + f.seek(0) + exit = True + else: + f.seek(step, os.SEEK_END) + data = f.read().strip() + if data.count('\n') >= window: + break + else: + block -= 1 + return data.splitlines()[-window:] + + def update_files(self): + ls = [] + for name in self.listdir(): + absname = os.path.realpath(os.path.join(self.folder, name)) + try: + st = os.stat(absname) + except EnvironmentError as err: + if err.errno != errno.ENOENT: + raise + else: + if not stat.S_ISREG(st.st_mode): + continue + fid = self.get_file_id(st) + ls.append((fid, absname)) + + # check existent files + for fid, file in list(iter(self.files_map.items())): + # next(iter(graph.items())) + try: + st = os.stat(file.name) + except EnvironmentError as err: + if err.errno == errno.ENOENT: + self.unwatch(file, fid) + else: + raise + else: + if fid != self.get_file_id(st): + # same name but different file (rotation); reload it. + self.unwatch(file, fid) + self.watch(file.name) + + # add new ones + for fid, fname in ls: + if fid not in self.files_map: + self.watch(fname) + + def readfile(self, file): + lines = file.readlines() + if lines: + self.callback(file.name, lines) + + def watch(self, fname): + try: + file = open(fname, "r") + fid = self.get_file_id(os.stat(fname)) + except EnvironmentError as err: + if err.errno != errno.ENOENT: + raise + else: + self.log("watching logfile %s" % fname) + self.files_map[fid] = file + + def unwatch(self, file, fid): + # file no longer exists; if it has been renamed + # try to read it for the last time in case the + # log rotator has written something in it. + lines = self.readfile(file) + self.log("un-watching logfile %s" % file.name) + del self.files_map[fid] + if lines: + self.callback(file.name, lines) + + @staticmethod + def get_file_id(st): + return "%xg%x" % (st.st_dev, st.st_ino) + + def close(self): + for id, file in list(iter(self.files_map.items())): + file.close() + self.files_map.clear()