diff --git a/aprsd/flask.py b/aprsd/flask.py
index 017b6eb..e8fc0aa 100644
--- a/aprsd/flask.py
+++ b/aprsd/flask.py
@@ -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
diff --git a/aprsd/main.py b/aprsd/main.py
index 99cf0c9..ca47e4b 100644
--- a/aprsd/main.py
+++ b/aprsd/main.py
@@ -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
diff --git a/aprsd/threads.py b/aprsd/threads.py
index 8592aa5..e682e89 100644
--- a/aprsd/threads.py
+++ b/aprsd/threads.py
@@ -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,
}
diff --git a/aprsd/utils.py b/aprsd/utils.py
index e2acd2b..1f55e06 100644
--- a/aprsd/utils.py
+++ b/aprsd/utils.py
@@ -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,
diff --git a/aprsd/web/static/css/prism.css b/aprsd/web/static/css/prism.css
new file mode 100644
index 0000000..8511262
--- /dev/null
+++ b/aprsd/web/static/css/prism.css
@@ -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;
+}
diff --git a/aprsd/web/static/js/logs.js b/aprsd/web/static/js/logs.js
new file mode 100644
index 0000000..f85d292
--- /dev/null
+++ b/aprsd/web/static/js/logs.js
@@ -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 + "
");
+ var div = document.getElementById('logContainer');
+ div.scrollTop = div.scrollHeight;
+}
diff --git a/aprsd/web/static/js/prism.js b/aprsd/web/static/js/prism.js
new file mode 100644
index 0000000..f232b27
--- /dev/null
+++ b/aprsd/web/static/js/prism.js
@@ -0,0 +1,2247 @@
+/* PrismJS 1.24.1
+https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript+json+json5+log&plugins=show-language+toolbar */
+///
+
+var _self = (typeof window !== 'undefined')
+ ? window // if in browser
+ : (
+ (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope)
+ ? self // if in worker
+ : {} // if in node js
+ );
+
+/**
+ * Prism: Lightweight, robust, elegant syntax highlighting
+ *
+ * @license MIT
+ * @author Lea Verou
+ * @namespace
+ * @public
+ */
+var Prism = (function (_self) {
+
+ // Private helper vars
+ var lang = /\blang(?:uage)?-([\w-]+)\b/i;
+ var uniqueId = 0;
+
+ // The grammar object for plaintext
+ var plainTextGrammar = {};
+
+
+ var _ = {
+ /**
+ * By default, Prism will attempt to highlight all code elements (by calling {@link Prism.highlightAll}) on the
+ * current page after the page finished loading. This might be a problem if e.g. you wanted to asynchronously load
+ * additional languages or plugins yourself.
+ *
+ * By setting this value to `true`, Prism will not automatically highlight all code elements on the page.
+ *
+ * You obviously have to change this value before the automatic highlighting started. To do this, you can add an
+ * empty Prism object into the global scope before loading the Prism script like this:
+ *
+ * ```js
+ * window.Prism = window.Prism || {};
+ * Prism.manual = true;
+ * // add a new
-
-
-
-
@@ -16,14 +12,17 @@
+
+
+
-