1
0
mirror of https://github.com/craigerl/aprsd.git synced 2025-09-04 22:27:50 -04:00

Added Logfile tab in Admin ui

This patch adds a live view of the aprsd logfile in
the admin ui.  This uses a new Log QueueHandler and the
threads.logging_queue to push log entries into a queue.
The flask websockets server will push those log entries up
to a connected client browser.
This commit is contained in:
Hemna 2021-09-07 13:13:36 -04:00
parent 4f088e0a4a
commit d6b3df93f1
8 changed files with 2560 additions and 12 deletions

View File

@ -486,6 +486,64 @@ class SendMessageNamespace(Namespace):
LOG.debug(f"WS json {data}")
class LogMonitorThread(threads.APRSDThread):
def __init__(self):
super().__init__("LogMonitorThread")
def loop(self):
global socketio
try:
record = threads.logging_queue.get(block=True, timeout=5)
json_record = self.json_record(record)
socketio.emit(
"log_entry", json_record,
namespace="/logs",
)
except Exception:
# Just ignore thi
pass
return True
def json_record(self, record):
entry = {}
entry["filename"] = record.filename
entry["funcName"] = record.funcName
entry["levelname"] = record.levelname
entry["lineno"] = record.lineno
entry["module"] = record.module
entry["name"] = record.name
entry["pathname"] = record.pathname
entry["process"] = record.process
entry["processName"] = record.processName
if hasattr(record, "stack_info"):
entry["stack_info"] = record.stack_info
else:
entry["stack_info"] = None
entry["thread"] = record.thread
entry["threadName"] = record.threadName
entry["message"] = record.getMessage()
return entry
class LoggingNamespace(Namespace):
def on_connect(self):
global socketio
LOG.debug("Web socket connected")
socketio.emit(
"connected", {"data": "/logs Connected"},
namespace="/logs",
)
self.log_thread = LogMonitorThread()
self.log_thread.start()
def on_disconnect(self):
LOG.debug("WS Disconnected")
self.log_thread.stop()
def setup_logging(config, flask_app, loglevel, quiet):
flask_log = logging.getLogger("werkzeug")
@ -548,4 +606,5 @@ def init_flask(config, loglevel, quiet):
# eventlet.monkey_patch()
socketio.on_namespace(SendMessageNamespace("/sendmsg", config=config))
socketio.on_namespace(LoggingNamespace("/logs"))
return socketio, flask_app

View File

@ -195,6 +195,20 @@ def setup_logging(config, loglevel, quiet):
imap_logger.setLevel(log_level)
imap_logger.addHandler(fh)
if (
utils.check_config_option(
config, ["aprsd", "web", "enabled"],
default_fail=False,
)
):
qh = logging.handlers.QueueHandler(threads.logging_queue)
q_log_formatter = logging.Formatter(
fmt=utils.QUEUE_LOG_FORMAT,
datefmt=utils.QUEUE_DATE_FORMAT,
)
qh.setFormatter(q_log_formatter)
LOG.addHandler(qh)
if not quiet:
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(log_formatter)
@ -506,10 +520,7 @@ def server(
keepalive = threads.KeepAliveThread(config=config)
keepalive.start()
try:
web_enabled = utils.check_config_option(config, ["aprsd", "web", "enabled"])
except Exception:
web_enabled = False
web_enabled = utils.check_config_option(config, ["aprsd", "web", "enabled"], default_fail=False)
if web_enabled:
flask_enabled = True

View File

@ -17,6 +17,7 @@ RX_THREAD = "RX"
EMAIL_THREAD = "Email"
rx_msg_queue = queue.Queue(maxsize=20)
logging_queue = queue.Queue(maxsize=50)
msg_queues = {
"rx": rx_msg_queue,
}

View File

@ -26,12 +26,17 @@ LOG_LEVELS = {
"DEBUG": logging.DEBUG,
}
DEFAULT_DATE_FORMAT = "%m/%d/%Y %I:%M:%S %p"
DEFAULT_LOG_FORMAT = (
"[%(asctime)s] [%(threadName)-12s] [%(levelname)-5.5s]"
"[%(asctime)s] [%(threadName)-20.20s] [%(levelname)-5.5s]"
" %(message)s - [%(pathname)s:%(lineno)d]"
)
DEFAULT_DATE_FORMAT = "%m/%d/%Y %I:%M:%S %p"
QUEUE_DATE_FORMAT = "[%m/%d/%Y] [%I:%M:%S %p]"
QUEUE_LOG_FORMAT = (
"%(asctime)s [%(threadName)-20.20s] [%(levelname)-5.5s]"
" %(message)s - [%(pathname)s:%(lineno)d]"
)
# an example of what should be in the ~/.aprsd/config.yml
DEFAULT_CONFIG_DICT = {
@ -288,7 +293,7 @@ def conf_option_exists(conf, chain):
def check_config_option(config, chain, default_fail=None):
result = conf_option_exists(config, chain.copy())
if not result:
if result is None:
raise Exception(
"'{}' was not in config file".format(
chain,

View File

@ -0,0 +1,189 @@
/* PrismJS 1.24.1
https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+log&plugins=show-language+toolbar */
/**
* prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
* Based on https://github.com/chriskempson/tomorrow-theme
* @author Rose Pritchard
*/
code[class*="language-"],
pre[class*="language-"] {
color: #ccc;
background: none;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #2d2d2d;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #999;
}
.token.punctuation {
color: #ccc;
}
.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
color: #e2777a;
}
.token.function-name {
color: #6196cc;
}
.token.boolean,
.token.number,
.token.function {
color: #f08d49;
}
.token.property,
.token.class-name,
.token.constant,
.token.symbol {
color: #f8c555;
}
.token.selector,
.token.important,
.token.atrule,
.token.keyword,
.token.builtin {
color: #cc99cd;
}
.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
color: #7ec699;
}
.token.operator,
.token.entity,
.token.url {
color: #67cdcc;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.inserted {
color: green;
}
div.code-toolbar {
position: relative;
}
div.code-toolbar > .toolbar {
position: absolute;
top: .3em;
right: .2em;
transition: opacity 0.3s ease-in-out;
opacity: 0;
}
div.code-toolbar:hover > .toolbar {
opacity: 1;
}
/* Separate line b/c rules are thrown out if selector is invalid.
IE11 and old Edge versions don't support :focus-within. */
div.code-toolbar:focus-within > .toolbar {
opacity: 1;
}
div.code-toolbar > .toolbar > .toolbar-item {
display: inline-block;
}
div.code-toolbar > .toolbar > .toolbar-item > a {
cursor: pointer;
}
div.code-toolbar > .toolbar > .toolbar-item > button {
background: none;
border: 0;
color: inherit;
font: inherit;
line-height: normal;
overflow: visible;
padding: 0;
-webkit-user-select: none; /* for button */
-moz-user-select: none;
-ms-user-select: none;
}
div.code-toolbar > .toolbar > .toolbar-item > a,
div.code-toolbar > .toolbar > .toolbar-item > button,
div.code-toolbar > .toolbar > .toolbar-item > span {
color: #bbb;
font-size: .8em;
padding: 0 .5em;
background: #f5f2f0;
background: rgba(224, 224, 224, 0.2);
box-shadow: 0 2px 0 0 rgba(0,0,0,0.2);
border-radius: .5em;
}
div.code-toolbar > .toolbar > .toolbar-item > a:hover,
div.code-toolbar > .toolbar > .toolbar-item > a:focus,
div.code-toolbar > .toolbar > .toolbar-item > button:hover,
div.code-toolbar > .toolbar > .toolbar-item > button:focus,
div.code-toolbar > .toolbar > .toolbar-item > span:hover,
div.code-toolbar > .toolbar > .toolbar-item > span:focus {
color: inherit;
text-decoration: none;
}

View File

@ -0,0 +1,26 @@
function init_logs() {
const socket = io("/logs");
socket.on('connect', function () {
console.log("Connected to logs socketio");
});
socket.on('connected', function(msg) {
console.log("Connected to /logs");
console.log(msg);
});
socket.on('log_entry', function(data) {
update_logs(data);
});
};
function update_logs(data) {
var code_block = $('#logtext')
entry = data["message"]
const html_pretty = Prism.highlight(entry, Prism.languages.log, 'log');
code_block.append(html_pretty + "<br>");
var div = document.getElementById('logContainer');
div.scrollTop = div.scrollHeight;
}

2247
aprsd/web/static/js/prism.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,6 @@
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script src="https://cdn.socket.io/4.1.2/socket.io.min.js" integrity="sha384-toS6mmwu70G0fw54EGlWWeA4z3dyJ+dlXBtSURSKN4vyRFOcxd3Bzjj/AoOwY+Rg" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/prism.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/components/prism-json.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/prismjs@1.23.0/themes/prism-tomorrow.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.bundle.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css">
@ -16,14 +12,17 @@
<link rel="stylesheet" href="/static/css/index.css">
<link rel="stylesheet" href="/static/css/tabs.css">
<link rel="stylesheet" href="/static/css/prism.css">
<script src="/static/js/prism.js"></script>
<script src="/static/js/main.js"></script>
<script src="/static/js/charts.js"></script>
<script src="/static/js/tabs.js"></script>
<script src="/static/js/send-message.js"></script>
<script src="/static/js/packets.js"></script>
<script src="/static/js/logs.js"></script>
<script type="text/javascript"">
<script type="text/javascript">
var initial_stats = {{ initial_stats|tojson|safe }};
var memory_chart = null
@ -35,6 +34,7 @@
start_update();
start_charts();
init_messages();
init_logs();
$("#toggleStats").click(function() {
$("#jsonstats").fadeToggle(1000);
@ -48,6 +48,10 @@
$("#configjson").html(html_pretty);
$("#jsonstats").fadeToggle(1000);
//var log_text_pretty = $('#logtext').text();
//const log_pretty = Prism.highlight( log_text_pretty, Prism.languages.log, 'log');
//$('#logtext').html(log_pretty);
$('.ui.accordion').accordion({exclusive: false});
$('.menu .item').tab('change tab', 'charts-tab');
});
@ -79,6 +83,7 @@
<div class="item" data-tab="plugin-tab">Plugins</div>
<div class="item" data-tab="config-tab">Config</div>
<div class="item" data-tab="send-tab">Send Message</div>
<div class="item" data-tab="log-tab">LogFile</div>
<div class="item" data-tab="raw-tab">Raw JSON</div>
</div>
@ -171,6 +176,11 @@
</div>
</div>
<div class="ui bottom attached tab segment" data-tab="log-tab">
<h3 class="ui dividing header">LOGFILE</h3>
<pre id="logContainer" style="height: 600px;overflow-y:auto;overflow-x:auto;"><code id="logtext" class="language-log" ></code></pre>
</div>
<div class="ui bottom attached tab segment" data-tab="raw-tab">
<h3 class="ui dividing header">Raw JSON</h3>
<pre id="jsonstats" class="language-json">{{ stats|safe }}</pre>