Commit e6ed77c7 authored by Alain Takoudjou's avatar Alain Takoudjou

monitor: tail -f the logs on monitoring stack (one file or many files)

parent 9a01f918
......@@ -44,7 +44,7 @@ recipe = slapos.recipe.template
url = ${:_profile_base_location_}/monitor.cfg.in
output = ${buildout:directory}/monitor.cfg
filename = monitor.cfg
md5sum = 307568cd064eca4417a8378f3275af7c
md5sum = 39c0b45d08399cf4a74fa586465fe8dc
mode = 0644
[monitor-bin]
......@@ -60,7 +60,7 @@ mode = 0644
recipe = hexagonit.recipe.download
url = ${:_profile_base_location_}/${:filename}
download-only = true
md5sum = 2d48f8b8e01fa0fdde964ed1c1547f05
md5sum = e9594ca1d44d98cb0e54a4f9c9527945
filename = cgi-httpd.conf.in
mode = 0644
......@@ -90,6 +90,14 @@ md5sum = 39f65de761e50909ea01fb401fb9475d
filename = information.html.in
mode = 0644
[logfile-cgi]
recipe = hexagonit.recipe.download
url = ${:_profile_base_location_}/webfile-directory/${:filename}
download-only = true
md5sum = deeaafd6d370447632eb0d41606f4bbb
filename = logfile.cgi.in
mode = 0644
[status-cgi]
recipe = hexagonit.recipe.download
url = ${:_profile_base_location_}/webfile-directory/${:filename}
......@@ -147,7 +155,7 @@ mode = 0644
recipe = hexagonit.recipe.download
url = ${:_profile_base_location_}/${:filename}
download-only = true
md5sum = 91a1a46bcdb7024035a734482c8c7bc9
md5sum = 22b530ace6497dd1011ee21342fcf4ba
filename = logTools.py
mode = 0644
......
......@@ -24,6 +24,7 @@ LoadModule authn_file_module modules/mod_authn_file.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule env_module modules/mod_env.so
# SSL Configuration
<IfDefine !SSLConfigured>
......@@ -38,6 +39,9 @@ SSLProtocol -ALL +SSLv3 +TLSv1
SSLHonorCipherOrder On
SSLCipherSuite RC4-SHA:HIGH:!ADH
</IfDefine>
{% for key, value in environment_dict.items() -%}
SetEnv {{ key }} "{{ value }}"
{% endfor -%}
SSLEngine On
ScriptSock {{ httpd_configuration.get('cgid-pid-file') }}
<Directory {{ directory.get('www') }}>
......
......@@ -167,3 +167,72 @@ def generateRSS(db_path, name, rss_path, url_link, limit=10):
with open(rss_path, 'w') as rss_ouput:
rss_ouput.write(rss_feed.to_xml())
def tail(f, lines=20):
"""
Returns the last `lines` lines of file `f`. It is an implementation of tail -f n.
"""
BUFSIZ = 1024
f.seek(0, 2)
bytes = f.tell()
size = lines + 1
block = -1
data = []
while size > 0 and bytes > 0:
if bytes - BUFSIZ > 0:
# Seek back one whole BUFSIZ
f.seek(block * BUFSIZ, 2)
# read BUFFER
data.insert(0, f.read(BUFSIZ))
else:
# file too small, start from begining
f.seek(0, 0)
# only read what was not read
data.insert(0, f.read(bytes))
linesFound = data[0].count('\n')
size -= linesFound
bytes -= BUFSIZ
block -= 1
return '\n'.join(''.join(data).splitlines()[-lines:])
def readFileFrom(f, lastPosition, limit=20000):
"""
Returns the last lines of file `f`, from position lastPosition.
and the last position
limit = max number of characters to read
"""
BUFSIZ = 1024
f.seek(0, 2)
# XXX-Marco do now shadow 'bytes'
bytes = f.tell()
block = -1
data = ""
length = bytes
truncated = False # True if a part of log data has been truncated
if (lastPosition <= 0 and length > limit) or (length - lastPosition > limit):
lastPosition = length - limit
truncated = True
size = bytes - lastPosition
while bytes > lastPosition:
if abs(block * BUFSIZ) <= size:
# Seek back one whole BUFSIZ
f.seek(block * BUFSIZ, 2)
data = f.read(BUFSIZ) + data
else:
margin = abs(block * BUFSIZ) - size
if length < BUFSIZ:
f.seek(0, 0)
else:
seek = block * BUFSIZ + margin
f.seek(seek, 2)
data = f.read(BUFSIZ - margin) + data
bytes -= BUFSIZ
block -= 1
f.close()
return {
'content': data,
'position': length,
'truncated': truncated
}
......@@ -179,6 +179,22 @@ context =
key monitoring_file_json monitor-parameters:json-path
raw python_executable ${buildout:executable}
[deploy-logfile-cgi]
recipe = slapos.recipe.template:jinja2
template = ${logfile-cgi:location}/${logfile-cgi:filename}
rendered = $${monitor-directory:monitoring-cgi}/$${:filename}
filename = Files.cgi
script-path= monitoring/Files.cgi
mode = 0744
context =
key monitor_bin monitor-parameters:executable
key script_path :script-path
section base_folder_list log-folder-cgi
raw python_executable ${buildout:directory}/bin/${extra-eggs:interpreter}
[log-folder-cgi]
log-folder = $${monitor-directory:log}
[make-rss]
recipe = slapos.recipe.template:jinja2
template = ${make-rss-script:output}
......@@ -353,6 +369,9 @@ listening-ip = $${slap-parameters:ipv6-random}
certificate = $${ca-httpd:cert-file}
key = $${ca-httpd:key-file}
[httpd-environment]
PYTHONPATH = ${log-tools:location}
[monitor-httpd-configuration-file]
recipe = slapos.recipe.template:jinja2
template = ${monitor-httpd-template:destination}/${monitor-httpd-template:filename}
......@@ -363,6 +382,7 @@ context =
section monitor_parameters monitor-parameters
section httpd_configuration monitor-httpd-configuration
section monitor_rewrite_rule monitor-rewrite-rule
section environment_dict httpd-environment
[cgi-httpd-wrapper]
recipe = slapos.cookbook:wrapper
......
#!{{ python_executable }}
import cgi
import cgitb
import json
import os
import subprocess
import logTools
import codecs
cgitb.enable(display=0, logdir="/tmp/cgi.log")
form = cgi.FieldStorage()
base_folder_list = {{ base_folder_list }}
script_path = "{{ script_path }}"
logpath = form.getvalue("path", "")
size = int(form.getvalue("size", "200"))
print """<html><head>
<link rel="stylesheet" href="static/pure-min.css">
<link rel="stylesheet" href="static/style.css">
<script src="static/jquery-1.10.2.min.js"></script>
<style>
.head{
background-color:#0078e7;
border:0px solid #ffffff;
text-align:left;
border-width:0px 0px 1px 1px;
font-size:18px;
font-family:Helvetica;
font-weight:normal;
color:#ffffff;
display: block;
padding: 10px;
margin: 0;
}
.box{
border: 1px solid #e8eaed;
padding: 5px;
}
textarea {width: 100%; height: 470px; margin-top: 10px;}
ul {margin:0px; padding: 0px; list-style: none;}
.button {margin-top: 5px;}
.button div {margin: 0; margin-right: 10px; float: left; }
</style>
<script language="javascript" type="text/javascript">
$(document).ready(function () {
$(".file").click(function() {
var child = $(this).children('input[type=checkbox]');
if (! $(child).is(':checked') ) {
$(child).prop( "checked", true );
}
else {$(child).prop( "checked", false );}
});
$( "#open" ).click(function() {
var file_list = "";
$(".file").each(function () {
if ($(this).children('input[type=checkbox]').is(':checked') ) {
file_list += $(this).attr("rel") + "#";
}
});
if (file_list == "") { return false;}
$("#path").val(file_list);
$( "#log_form" ).submit();
return false;
});
$( "#check" ).click(function() {
$(".file").each(function () {
var child = $(this).children('input[type=checkbox]');
if (! $(child).is(':checked') ) {
$(child).prop( "checked", true );
}
});
return false;
});
$( "#uncheck" ).click(function() {
$(".file").each(function () {
var child = $(this).children('input[type=checkbox]');
if ($(child).is(':checked') ) {
$(child).prop( "checked", false );
}
});
return false;
});
$( "#reload" ).click(function() {
$( "#log_form" ).submit();
return false;
});
$( "#return" ).click(function() {
$("#path").val("");
$( "#log_form" ).submit();
return false;
});
var textarea = $("#logcontent")
if (textarea != undefined) {
$(textarea).animate({ scrollTop: $(textarea)[0].scrollHeight - $(textarea).height() }, "slow");
}
});
</script>
</head><body>"""
if not logpath:
print """
<form action="/index.cgi" method="post" class="pure-form-aligned" id="log_form">
<input type="hidden" name="posting-script" value="%s" />
<div class="box pure-menu pure-menu-open">
<h2 class="head">Select file(s) in the list bellow</h2>
<ul>""" % script_path
for base_folder in base_folder_list.values():
if os.path.exists(base_folder):
for filename in os.listdir(base_folder):
path = os.path.join(base_folder, filename)
if not os.path.isdir(path):
print """ <li>
<a href="#" class="script file" rel="%s" title="%s">
<input type="checkbox" />
%s</a></li>""" % (
path, path, filename)
else:
# accept a folder containing log files
for sub_filename in os.listdir(path):
sub_path = os.path.join(path, sub_filename)
if os.path.isdir(sub_path):
continue
print """ <li><a href="#" class="script file" rel="%s" title="%s">%s</a></li>""" % (
sub_path, sub_path, sub_filename)
print """ </ul>
</div>
<div class="button">
<div><label for="size">Lines: </label><select name="size" id="size">
<option value="50" selected>50</option>
<option value="100">100</option>
<option value="200">200</option>
<option value="500">500</option>
<option value="1500">1500</option>
</select></div>
<button type="button" class="pure-button pure-button-primary" id="uncheck">Uncheck All</button>
<button type="button" class="pure-button pure-button-primary" id="check">Check All</button>
<button type="button" class="pure-button pure-button-primary" id="open">Open File(s)</button>
</div>
<div style='clear:both'></div>
<input type="hidden" name="path" id="path" value="" />
</form>"""
else:
path_list = [x for x in logpath.split('#') if x]
log_content = ""
title = ""
for filepath in path_list:
if os.path.exists(filepath) and os.path.isfile(filepath):
title += " " + filepath.split('/')[-1:][0]
try:
content = logTools.tail(codecs.open(filepath, "r", "utf_8"), size)
except Exception, e:
content = str(e)
if content:
log_content += "TAIL FILE %s >>>>\n\n" % filepath
log_content += content + "\n\n\n"
print """
<form action="/index.cgi" method="post" class="pure-form-aligned" id="log_form">
<input type="hidden" name="posting-script" value="%s" />
<input type="hidden" name="path" id="path" value="%s" />
</form>
<div class="box">
<h2 class="head">Tail: %s </h2>
<div class="button">
<button type="submit" class="pure-button pure-button-primary" id="return">Return</button>
<button type="submit" class="pure-button pure-button-primary" id="reload">Refresh</button>
</div>
<div style='clear:both'></div>
<textarea id="logcontent">%s</textarea>
</div>
""" % (script_path, logpath, title, log_content)
print """
</body></html>"""
\ No newline at end of file
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment