Skip to content

Instantly share code, notes, and snippets.

@CodeAlDente
Last active January 22, 2026 13:42
Show Gist options
  • Select an option

  • Save CodeAlDente/1e841d820284564f5b8e0f2482c612a7 to your computer and use it in GitHub Desktop.

Select an option

Save CodeAlDente/1e841d820284564f5b8e0f2482c612a7 to your computer and use it in GitHub Desktop.
Adds a download button to the listmonk admin logs page to export the currently visible logs as a text file.
// ==UserScript==
// @name listmonk – Logs Downloader
// @author CodeAlDente (https://github.com/CodeAlDente)
// @namespace https://gist.github.com/CodeAlDente/1e841d820284564f5b8e0f2482c612a7
// @version 1.0.0
// @description Adds a native “Download logs” button to the listmonk admin logs page. When clicked, the visible logs are exported as a text file.
// @match https://*/admin/settings/logs
// @run-at document-idle
// @grant none
// @license MIT
// @homepageURL https://gist.github.com/CodeAlDente/1e841d820284564f5b8e0f2482c612a7
// @supportURL https://gist.github.com/CodeAlDente/1e841d820284564f5b8e0f2482c612a7
// ==/UserScript==
(function () {
'use strict';
// Extra guard in case the URL matching ever changes.
// The script should never run outside the listmonk logs page.
if (!location.pathname.startsWith('/admin/settings/logs')) {
return;
}
// Formats a Date object into a filesystem-friendly timestamp
// used for the exported log filename.
function formatDate(date) {
const pad = n => String(n).padStart(2, '0');
return (
date.getFullYear() + '-' +
pad(date.getMonth() + 1) + '-' +
pad(date.getDate()) + '_' +
pad(date.getHours()) + '-' +
pad(date.getMinutes()) + '-' +
pad(date.getSeconds())
);
}
// Reads the currently visible log lines from the DOM and
// turns them into a plain text representation.
function collectLogs() {
const lines = document.querySelectorAll('.log-view .lines .line');
if (!lines.length) {
alert('No log lines found.');
return '';
}
return Array.from(lines).map(line => {
const ts = line.querySelector('.timestamp')?.innerText.replace(/\s+/g, ' ') ?? '';
const file = line.querySelector('.file')?.innerText ?? '';
const msg = line.querySelector('.log-message')?.innerText ?? '';
return `${ts}${file}${msg}`.trim();
}).join('\n');
}
// Creates a local text file and triggers a download in the browser.
// Everything happens client-side; nothing is sent anywhere.
function downloadLogs() {
const text = collectLogs();
if (!text) return;
const now = new Date();
const hostname = location.hostname.replace(/[^a-zA-Z0-9.-]/g, '_');
const filename = `listmonk-logs_${hostname}_${formatDate(now)}.txt`;
const blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
URL.revokeObjectURL(url);
a.remove();
}
// Injects the "Download logs" button into the listmonk UI.
// Uses existing listmonk/Bulma button styles to blend in.
function injectButton() {
if (document.getElementById('listmonk-log-download-btn')) return;
const logsSection = document.querySelector('section.logs');
if (!logsSection) return;
const title = logsSection.querySelector('h1.title');
if (!title) return;
const btn = document.createElement('button');
btn.id = 'listmonk-log-download-btn';
btn.type = 'button';
btn.className = 'button is-primary';
btn.style.marginBottom = '1rem';
const span = document.createElement('span');
span.textContent = 'Download logs';
btn.appendChild(span);
btn.addEventListener('click', downloadLogs);
// Place the button directly below the page title,
// matching the layout of other admin actions.
title.insertAdjacentElement('afterend', btn);
}
// listmonk loads parts of the page dynamically.
// This observer waits until the logs and title exist
// before injecting the button.
const observer = new MutationObserver(() => {
if (
document.querySelector('.log-view .lines .line') &&
document.querySelector('section.logs h1.title')
) {
injectButton();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();
@CodeAlDente
Copy link
Author

How to install

  1. Install a userscript manager such as Tampermonkey in your browser.
  2. Open the Gist and click Raw, then install the script when prompted.
  3. Open the listmonk admin logs page (/admin/settings/logs) — the Download logs button will appear automatically.

Optional (recommended)
For extra safety, you can restrict the script to your own listmonk domain by editing the @match line in the script, or by adjusting the include/match settings in your userscript manager after installation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment