Log Viewer Page
The Log Viewer is the read-only screen at /preference/log, route name preference-log, that
renders the web service's own log file for the current day. It is not a Control-Tree page and takes
no ?tree= query: the whole page is one toolbar over one scrolling text area. Despite the route
path it is not an entry of the Preference dropdown, and it is not under /util/.
Reaching the Page
The only navigation into the page is the Show Log button on the menu bar. It sits in the bar's
right-hand group, after the active-page title and its separator and before the logout button, and it
carries the article icon with the tooltip View the application log. The button is rendered
unconditionally — no route, project, licence or authentication state hides it — so it is reachable
from every page in the shell. The Preference dropdown carries the Language submenu, the CSV and CL
Controller visibility checkboxes and Show Physics Options, and no log entry; the Page dropdown does
not list the route either.
The route sits inside the main layout, so the page renders under the same menu bar and footer as
every other page, and it is behind the login gate whenever that gate is on — the router's navigation
guard admits only the login route itself while the session is unauthenticated. The route carries an
i18n title key, so the browser tab reads Log Viewer - HiNC and the menu bar's active-page
indicator reads Log Viewer.
What It Reads
Two endpoints, both on the project controller, and both served from the same file:
| Request | Answer |
|---|---|
GET /api/project/logs |
{ date, content } for today's file; 404 with a message when there is none |
GET /api/project/download-log |
the same file as a text/plain attachment; 404 with the same message when there is none |
Both compose logs/log-{yyyy-MM-dd}.txt beneath the process's current working directory and date the
name from the server's local clock. Neither supports a range or a tail: the whole file is read into
memory and returned in full on every call, so an auto-refresh at a short interval re-transfers the
entire log each tick. Any failure inside either handler answers 400 with the exception message.
The two client wrappers part company on that 404. The wrapper over logs normalises it into an
empty result rather than an error, which is what gives the page a clean “no log yet” state
instead of a red panel; every other non-OK status there is raised as a typed error carrying the HTTP
status and the response body. The download wrapper normalises nothing — 404 included, every non-OK
status becomes that same typed error, a path the disabled-without-content Download button makes
hard to reach. Because the controller declares no anonymous carve-out, both endpoints fall under the
authenticated-user fallback policy when the login gate is enabled.
The date badge shows whatever date the last answer reported. On the 404 path that date is not the
server's — the client fills it from the browser's own clock in UTC — so with no log file present the
badge can read a different day than the file name the server would have used.
Refresh and the Auto Interval
The page loads once on mount. After that every reload is driven by one of three things: the
Refresh button, the Retry button inside the error panel, or the auto-refresh timer.
Auto-refresh is off by default. The Auto toggle arms it and the interval select beside it chooses
the period from a fixed list of 2 s, 5 s, 10 s and 30 s, defaulting to five seconds; the
select is disabled while the toggle is off, but it keeps its value. The period is picked from that
dropdown rather than typed, so it takes effect on selection. A change to either control tears the
existing timer down, and a fresh one starts only while the toggle is still armed: an interval change
therefore takes effect immediately but also restarts the countdown, while disarming the toggle
simply stops the timer. Arming the toggle schedules the first automatic load one full interval
later; it does not fetch straight away.
Ticks are not coalesced with a request already in flight — each tick starts the same load unconditionally.
Neither the toggle nor the interval is persisted. They are page-local state, held in neither the browser's local storage nor the server-side user preferences, so a browser reload returns both to their defaults.
The Page Is Kept Alive
The shell caches routed pages in a keep-alive keyed on the project epoch. Leaving the Log Viewer
therefore deactivates it rather than unmounting it, and the page's teardown hook — the one that
stops the timer — does not run. Two consequences follow. An armed auto-refresh keeps polling the log
endpoint in the background after navigation away, until the keep-alive is rebuilt by a project change
or the browser reloads. And returning to the page does not re-run the initial load: the previous
content, its last loaded stamp, the toggle and the chosen interval are all still there, and the
view refreshes on the next tick or on Refresh.
Errors and Scroll Position
A failed load fills the page's error state and also pushes one foreground line onto the shell's routine-progress footer, so the failure is visible from any page. The error panel takes precedence over content in the text area, so a failed refresh hides the text that was already loaded — but only until the next attempt starts, because the error text is cleared before that request goes out. The content itself is never discarded: it reappears the moment the attempt begins, and the error panel returns only if that attempt fails as well. With auto-refresh armed against an endpoint that keeps failing, the text and the panel therefore alternate on every tick.
After a successful load the view scrolls to the bottom, but only while it is already stuck there — the scroll handler treats a distance of less than 30 px from the bottom as stuck. Scrolling up therefore freezes the position through an auto-refresh, and scrolling back to the bottom re-arms the follow behaviour.
Level Filtering, and Where the File Comes From
The page applies no filtering of its own. There is no level selector, no category selector, no search box and no date picker: it renders the file it is given, verbatim, in the order it was written. Which entries reach the file at all is decided at write time, by the host's logging filters rather than by anything on this screen — see Program and Hosting for those rules and how they differ from the console provider's.
The sink is a daily file logger provider registered at startup against a logs folder under the
process's current working directory. It writes one entry per line as
[yyyy-MM-dd HH:mm:ss.fff] [Level] Category - message, appends the exception on its own lines when
there is one, serialises appends behind a lock and swallows every I/O failure, on the principle that
logging must never fault a request. Logging scopes are not rendered into the file. The application
writes one Information line immediately after the host is built, so the file for the day the service
starts has content before the first request arrives and a freshly started service shows something
rather than an empty state. The sink recomposes the file name from the clock on every append, so a
service left running past midnight has no file for the new day until something is logged, and the
page shows its empty state until then.
Only the current day's file is reachable. Previous days' files stay on disk and the page offers no way to open one. A file that exists but is empty renders the same empty state as a missing one; only the date badge can separate the two, since the empty file's date comes from the server's answer and the missing file's from the browser's UTC clock.
The same controller also exposes a POST endpoint that appends a caller-supplied line to the same daily file. It writes with its own formatting rather than through the logger provider, and nothing in the shipped SPA calls it.
Copy, Download, and What the Page Cannot Do
Copy writes the loaded text to the system clipboard and reports the outcome as a toast, positive
or negative. Download fetches the file as a blob from the download endpoint and saves it
client-side under the name log-<date>.txt, repeating the date the last answer reported, and also
ends in a toast. Both buttons are disabled while there is nothing loaded.
The page cannot clear, truncate, rotate or delete the log — there is no destructive action on it at all, and no endpoint behind it that would perform one. It cannot reach another day, filter by level or text, or stream: refreshing is polling over the same whole-file request, with no hub connection and no server push. It is a viewer.
Layout
- Log Viewer Page — a viewport-locked page sized from the layout's measured header and footer, so
the window itself never scrolls
- Top Tool Bar
Log ViewerTitle- Date Badge — outlined; carries the date the last answer reported, and reads
todayonly before any answer has set one. Coloured as the primary accent while content is loaded and grey otherwise - Last-Loaded Caption —
last loaded: {time}, shown once a load has succeeded. The time is formatted in the application locale rather than the browser's, so it re-renders on a language switch; the hover title carries the same instant as an ISO 8601 UTC string - Line Count Caption — shown only while content is loaded, where it reads
{count} lines. The plural string'sno linesandone lineforms are out of reach: a zero count is exactly the state that hides the caption, and every writer ends its entry with a newline. The count is the number of newline-separated segments, so a file ending in a newline counts one more than it shows, and a multi-line exception counts as several AutoToggle — arms auto-refresh; off by default. TooltipRe-fetch the log file periodically- Interval Select —
2 s/5 s/10 s/30 s, defaulting to5 s; disabled whileAutois off RefreshButton — reloads at once, and shows a spinner in place of its label while the request is in flight. TooltipReload the log fileCopyButton — disabled without content. TooltipCopy log content to clipboardDownloadButton — disabled without content. TooltipDownload today's log file
- Separator
- Log Area — a dark, monospaced, scrolling region filling the rest of the page. Long lines are not
wrapped, so it scrolls in both directions. It shows exactly one of four things:
- Error Panel — an error icon, the failure message, and a
RetryButton that repeats the load - Empty Panel — a document icon,
No log file for today., and the hintLog file will appear once the webservice writes its first entry. - Log Content — the file's text, preformatted and read-only, with no folding, no line numbers and no per-line affordance of any kind
- Loading Spinner — with
Loading log...beneath it, shown while a load is in flight and nothing has been loaded yet
- Error Panel — an error icon, the failure message, and a
- Top Tool Bar
Source Code Path
See HiNC App Anatomy for git repository links.
Web Application
HiNC-2025-webservice (Quasar CLI SPA):
wwwroot-src/src/pages/LogViewerPage.vue— the whole page: the toolbar, the four states of the log area, the auto-refresh timer and its watcher, the stick-to-bottom scroll rule, and the copy and download handlers.wwwroot-src/src/api/logs.ts— the typed wrappers over the two endpoints, the404-to-empty normalisation with its browser-clock date fallback, and the error type carrying the HTTP status.wwwroot-src/src/router/routes.ts— thepreference/logpath, thepreference-logroute name and the title key the tab and the menu bar's active-page indicator resolve.wwwroot-src/src/router/index.ts— the login guard the route passes through, and the retitle hook that names the browser tab.wwwroot-src/src/components/AppMenuBar.vue— the always-visibleShow Logbutton in the bar's right-hand group, and the Preference dropdown that carries no log entry.wwwroot-src/src/layouts/MainLayout.vue— the keep-alive that caches this page rather than unmounting it, and the project epoch that rebuilds the cache.wwwroot-src/src/stores/appState.ts— the message action that forwards a load failure to the footer's foreground channel.wwwroot-src/src/stores/routineProgress.ts— the foreground-message store that failure lands in.wwwroot-src/src/utils/pageFit.ts— the page-height function that locks the page to the measured viewport so only the log area scrolls.wwwroot-src/src/i18n/en/logs.ts— the page's own English strings: the title, the empty and loading states, the plural line count and the tooltips.wwwroot-src/src/i18n/en/menu.ts— theShow Loglabel and its tooltip.wwwroot-src/src/i18n/en/common.ts— the sharedAuto,Refresh,CopyandDownloadlabels.wwwroot-src/src/i18n/en/routes.ts— theLog Viewerroute title.Environments/ProjectController.cs— the two endpoints the page reads, both resolving today's file under the working directory, plus the append endpoint nothing calls.Common/DailyFileLoggerProvider.cs— the sink: the per-day file, the entry format, the lock, the swallowed I/O failures and the null scope.Program.cs— registers that provider and its level filters, and writes the one startup entry right after the host is built.
See Also
- Main Panel — the menu bar whose right-hand
Show Logbutton is the only navigation into this page - Preference Menu Dropdown — the dropdown this route's path is named after, and which carries no log entry of its own