summaryrefslogtreecommitdiffstats
path: root/callback_plugins
diff options
context:
space:
mode:
authorSeth Vidal <skvidal@fedoraproject.org>2013-06-28 20:59:12 +0000
committerSeth Vidal <skvidal@fedoraproject.org>2013-06-28 20:59:12 +0000
commit20b83610b40dfe30807dd245ee5844bebbe49886 (patch)
treeb90eecdd7eeae7e68f1063b86e60347bfbb205ff /callback_plugins
parent39843350fa755a74b6d46648d6efe1294b3fe4e0 (diff)
downloadansible-20b83610b40dfe30807dd245ee5844bebbe49886.tar.gz
ansible-20b83610b40dfe30807dd245ee5844bebbe49886.tar.xz
ansible-20b83610b40dfe30807dd245ee5844bebbe49886.zip
add logdetail callback plugin - hopefully this will not break for us
Diffstat (limited to 'callback_plugins')
-rw-r--r--callback_plugins/logdetail.py256
1 files changed, 256 insertions, 0 deletions
diff --git a/callback_plugins/logdetail.py b/callback_plugins/logdetail.py
new file mode 100644
index 000000000..819837797
--- /dev/null
+++ b/callback_plugins/logdetail.py
@@ -0,0 +1,256 @@
+# (C) 2012, Michael DeHaan, <michael.dehaan@gmail.com>
+
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import time
+import json
+from ansible import utils
+
+TIME_FORMAT="%b %d %Y %H:%M:%S"
+
+MSG_FORMAT="%(now)s - %(count)s - %(category)s - %(name)s - %(data)s\n"
+
+LOG_PATH = '/var/log/ansible'
+
+
+class LogMech(object):
+ def __init__(self):
+ self.started = time.time()
+ self._pb_fn = None
+ self._last_task_start = None
+ self.play_info = {}
+ self.logpath = LOG_PATH
+ if not os.path.exists(self.logpath):
+ try:
+ os.makedirs(self.logpath)
+ except OSError, e:
+ if e.errno != 17:
+ raise
+
+ # checksum of full playbook?
+
+ @property
+ def playbook_id(self):
+ if self._pb_fn:
+ return os.path.basename(self._pb_fn).replace('.yml', '').replace('.yaml', '')
+ else:
+ return "Unknown-playbook"
+
+ @playbook_id.setter
+ def playbook_id(self, value):
+ self._pb_fn = value
+
+ @property
+ def logpath_play(self):
+ # this is all to get our path to look nice ish
+ day = time.strftime('%Y/%m/%d', time.localtime(self.started))
+ offset_in_sec = str(self.started - time.mktime(time.strptime(day, '%Y/%m/%d')))
+ path = os.path.normpath(self.logpath + '/' + day + '/' + self.playbook_id + '/' + offset_in_sec)
+
+ if not os.path.exists(path):
+ try:
+ os.makedirs(path)
+ except OSError, e:
+ if e.errno != 17: # if it is not dir exists then raise it up
+ raise
+
+ return path
+
+ def play_log(self, content):
+ # record out playbook.log
+ # include path to playbook, checksums, user running playbook
+ # any args we can get back from the invocation
+ fd = open(self.logpath_play + '/' + 'playbook.log', 'a')
+ fd.write('%s\n' % content)
+ fd.close()
+
+ def task_to_json(self, task):
+ res = {}
+ res['task_name'] = task.name
+ res['task_module'] = task.module_name
+ res['task_args'] = task.module_args
+ for k in ("delegate_to", "environment", "first_available_file",
+ "local_action", "notified_by", "notify", "only_if",
+ "register", "sudo", "sudo_user", "tags",
+ "transport", "when"):
+ v = getattr(task, k, None)
+ if v:
+ res['task_' + k] = v
+
+ return res
+
+ def log(self, host, category, data, task=None, count=0):
+ if not host:
+ host = 'HOSTMISSING'
+
+ name = data.get('module_name',None)
+
+
+ # we're in setup - move the invocation info up one level
+ if 'invocation' in data:
+ invoc = data['invocation']
+ if not name and 'module_name' in invoc:
+ name = invoc['module_name']
+
+ del(data['invocation'])
+ data.update(invoc)
+
+ if task:
+ name = task.name
+ data['task_start'] = self._last_task_start
+ data['task_end'] = time.time()
+ data.update(self.task_to_json(task))
+
+ if category == 'OK' and data.get('changed', False):
+ category = 'CHANGED'
+
+
+ fd = open(self.logpath_play + '/' + host + '.log', 'a')
+ now = time.strftime(TIME_FORMAT, time.localtime())
+ fd.write(MSG_FORMAT % dict(now=now, name=name, count=count, category=category, data=json.dumps(data)))
+ fd.close()
+
+
+logmech = LogMech()
+
+class CallbackModule(object):
+ """
+ logs playbook results, per host, in /var/log/ansible/hosts
+ """
+ def __init__(self):
+ self._task_count = 0
+ self._play_count = 0
+
+ def on_any(self, *args, **kwargs):
+ pass
+
+
+ def runner_on_failed(self, host, res, ignore_errors=False):
+ category = 'FAILED'
+ task = getattr(self,'task', None)
+ logmech.log(host, category, res, task, self._task_count)
+
+
+ def runner_on_ok(self, host, res):
+ category = 'OK'
+ task = getattr(self,'task', None)
+ logmech.log(host, category, res, task, self._task_count)
+
+
+ def runner_on_error(self, host, res):
+ category = 'ERROR'
+ task = getattr(self,'task', None)
+ logmech.log(host, category, res, task, self._task_count)
+
+ def runner_on_skipped(self, host, item=None):
+ category = 'SKIPPED'
+ task = getattr(self,'task', None)
+ res = {'item':item}
+ logmech.log(host, category, res, task, self._task_count)
+
+ def runner_on_unreachable(self, host, res):
+ category = 'UNREACHABLE'
+ task = getattr(self,'task', None)
+ logmech.log(host, category, res, task, self._task_count)
+
+ def runner_on_no_hosts(self):
+ pass
+
+ def runner_on_async_poll(self, host, res, jid, clock):
+ pass
+
+ def runner_on_async_ok(self, host, res, jid):
+ pass
+
+ def runner_on_async_failed(self, host, res, jid):
+ category = 'ASYNC_FAILED'
+ task = getattr(self,'task', None)
+ logmech.log(host, category, res, task, self._task_count)
+
+ def playbook_on_start(self):
+ pass
+
+ def playbook_on_notify(self, host, handler):
+ pass
+
+ def playbook_on_no_hosts_matched(self):
+ pass
+
+ def playbook_on_no_hosts_remaining(self):
+ pass
+
+ def playbook_on_task_start(self, name, is_conditional):
+ logmech._last_task_start = time.time()
+ self._task_count += 1
+
+ def playbook_on_vars_prompt(self, varname, private=True, prompt=None, encrypt=None, confirm=False, salt_size=None, salt=None, default=None):
+ pass
+
+ def playbook_on_setup(self):
+ self._task_count += 1
+ pass
+
+ def playbook_on_import_for_host(self, host, imported_file):
+ task = getattr(self,'task', None)
+ logmech.log(host, 'IMPORTED', imported_file, task)
+
+ def playbook_on_not_import_for_host(self, host, missing_file):
+ task = getattr(self,'task', None)
+ logmech.log(host, 'NOTIMPORTED', missing_file, task)
+
+ def playbook_on_play_start(self, pattern):
+ self._task_count = 0
+
+ play = getattr(self, 'play', None)
+ if play:
+ # figure out where the playbook FILE is
+ path = os.path.abspath(play.playbook.filename)
+
+ # tel the logger what the playbook is
+ logmech.playbook_id = path
+
+ # if play count == 0
+ # write out playbook info now
+ if not self._play_count:
+ pb_info = {}
+ pb_info['playbook'] = path
+ pb_info['userid'] = os.getlogin()
+ pb_info['extra_vars'] = play.playbook.extra_vars
+ pb_info['inventory'] = play.playbook.inventory.host_list
+ pb_info['playbook_checksum'] = utils.md5(path)
+ logmech.play_log(json.dumps(pb_info, indent=4))
+
+ self._play_count += 1
+ # then write per-play info that doesn't duplcate the playbook info
+ info = {}
+ info['play'] = play.name
+ info['hosts'] = play.hosts
+ info['transport'] = play.transport
+ info['number'] = self._play_count
+ logmech.play_info = info
+ logmech.play_log(json.dumps(info, indent=4))
+
+
+ def playbook_on_stats(self, stats):
+ results = {}
+ for host in stats.processed.keys():
+ results[host] = stats.summarize(host)
+ logmech.log(host, 'STATS', results[host])
+ logmech.play_log(json.dumps({'stats': results}, indent=4))
+ print ' logs written to: %s\n' % logmech.logpath_play
+
+