Log Viewer Page
The Log Viewer is the read-only screen at /preference/log, route name preference-log, that
renders this web service instance'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 Log Viewer entry of the menu bar's Help dropdown — a
support tool rather than a shop-floor one, so it sits beside the other two references the service
hosts instead of on the bar. It sits below Documentation, API Reference (Swagger) and a
separator, and is the dropdown's last entry unless a host built the SPA with Help items of its
own. Unlike the two above it, which open a same-origin reference in a
new tab and carry an open-in-new-tab icon, it is a router link to the preference-log route, so the
page opens in place, in the same tab, and the entry carries no icon. Its label is the route's own
title key — the one the browser tab resolves — rather than a menu string of its own. The entry 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, Show
Physics Options and the Optional Nodes & Charts submenu, and no log entry; no page switch names the
route either, so none is lit while the page is shown.
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. The menu bar prints no page title on
this page or any other; the name on screen is the page's own Log Viewer title row.
What It Reads
Two endpoints, both on the project controller, and both served from the same file — the one this instance is writing today:
| 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 named as the file is, for example log-Development-2026-09-23.txt; 404 with the same message when there is none |
Neither composes a path of its own: both ask the file log provider for today's file, so they follow
its configured directory and its instance name, and they open it with write sharing while the
provider's writer still holds it. The date comes 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: the provider's alias is File, so Logging:File:LogLevel in
configuration sets the file's levels apart from the console's Logging:LogLevel, over defaults the
host seeds in code — see Program and Hosting for those rules and how they combine.
The sink is DailyFileLoggerProvider, registered at startup. It writes
{Directory}/log-{instance}-{yyyy-MM-dd}.txt. The directory is Logging:File:Directory, logs by
default, a relative path being taken from the process's current working directory; the instance is
the host environment name — Development under both launch profiles — so several instances that
share a working directory keep separate files. A second process started under the same environment
name and directory cannot take the instance lock file, log-{instance}.lock, and writes
log-{instance}-{pid}-{yyyy-MM-dd}.txt instead of appending over the first one's lines; it says so on
its first line, and its own endpoints serve that file. A directory that cannot be created or written
falls back to logs under the working directory, and the first line this process writes there gives
the reason.
Each entry is one line, [yyyy-MM-dd HH:mm:ss.fff] [Level] Category - message, with the exception
appended on its own lines when there is one; logging scopes are not rendered into the file. A log call
only enqueues the line: one background writer thread owns the open file, so a call on a render or step
thread never waits on disk. A Critical entry waits until it is written, being often the last line a
failing process gets out. When the queue is full, entries are dropped and the next line that gets in
is preceded by a note of how many; a write that fails drops its line rather than faulting the caller.
Program.cs flushes the queue before the native core's teardown and shuts the writer down last, after
which lines are written synchronously.
The application writes a startup summary 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 writer opens the file named for each entry's date, 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. It also reopens today's file when that file is deleted under it, rather than writing on into an orphan.
Retention is Logging:File:RetainDays, 30 by default, with 0 keeping every file. At start and at each
day change the provider deletes this instance's files older than that, and files of the older
single-file naming, log-{yyyy-MM-dd}.txt, once they pass the same age; another instance's files
are left to that instance.
Only the current day's file is reachable. Previous days' files stay on disk until retention removes them, 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.
No endpoint appends to the file; the logger provider is its only writer.
What the File Holds
At the shipped levels the file is a record of what the instance did rather than a trace of every request:
- Startup — one summary line naming the instance, the build (the HiNc assembly version), the REST
API version, the working directory, the admin directory and whether the login gate is on; then the
host's own
Microsoft.Hosting.Lifetimelines — the listening address, the environment and the content root. The shutdown sequence logs its steps as well. - Project operations — one line per completed File-menu operation from the project controller,
Project load|reload|new|save|save-as|close: <path> in N ms. Load and reload add the load's error and warning counts, and a load with errors is a Warning: the project is open but not whole. - Runs —
Run N started: <project>, and when the run endsRun N finished|stopped|ended after S s: <project>, <steps> steps; errors NC / step / shell; warnings NC / step / shell; K repeated message(s) not logged, a Warning when the run raised errors. A run is a pace-player lock that arrives with a new run count; an idle one-shot recompute takes the lock without one and is not a run.endedis the outcome when the player was already finished as the run began, where a finish cannot be told from a stop. - Engine messages — every Warning and Error of the four session sinks, under the categories
Hi.Engine.Shell,Hi.Engine.Nc,Hi.Engine.StepandHi.Engine.NcManipulation, as<id> <anchor> <message>, the anchor beingSn nfor an NC sentence,S<step> Sn nfor a step (S<step>alone when the step has no sentence) and-for a shell message or an NC message with no sentence. One message id is logged at most 20 times per run — a step diagnostic can repeat on every step — and the run-end line counts the rest; messages outside a run are capped the same way per idle period. These lines outlive the rows of the sinks the Session Message Panel reads, which each sink's next clearing discards. - Sign-ins — a sign-in, a refused sign-in (a Warning) and a sign-out, each with the caller's address; see Login and Authentication.
- Refused hosts — a request addressed to a host name the service does not admit is answered
400by host filtering and leaves one line naming that host, under aMicrosoft.AspNetCore.HostFilteringcategory; see Program and Hosting for the names it admits. - Native core — the core's own log lines under
Hi.Native, at a level read from their wording: licence failures Critical, a GL, shader or EGL failure Error, Mesa software rasterisation Warning, routine records such as the OpenGL context Information. The library's licence abort line is Critical underHi.HiNcKits.LocalApp. - Crashes — an unhandled exception (Critical) and a faulted task nobody observed (Error), each with its exception.
- Connections and commands — the per-connection lifecycle of the canvases and the Execution page
(canvas initialisation, display engine create and remove, the first rendered frame, the Execution
page initialised), the playback transport commands other than start (pause, resume, run to line
end, run to next pace, stop and reset; a start shows as the
Run N startedline instead), and Tool House create, duplicate, rename and delete. A hub disconnect that carries an exception is a Warning.
Per-input, per-frame, visibility, compression, hub-connect and status-broadcast chatter, and the editors' per-field echoes, are logged at Debug and do not reach the file at the shipped levels.
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 — the page
names the blob itself, so the instance name in the endpoint's attachment name does not reach the saved
file — 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 Help entry 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— theLog Viewerentry below the Help dropdown's separator, followed only by the product module's Help items, and the Preference dropdown that carries no log entry.wwwroot-src/src/product/index.ts— the web service's ownHelpMenuItems,null, which leaves theLog Viewerentry last.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/common.ts— the sharedAuto,Refresh,CopyandDownloadlabels.wwwroot-src/src/i18n/en/routes.ts— theLog Viewerroute title, which is also the Help entry's label.Environments/ProjectController.cs— the two endpoints the page reads, both opening today's file through the provider, and the one line each project operation logs.Common/DailyFileLoggerProvider.cs— the sink: theFilealias, the per-instance file name, the instance lock and its pid fallback, the directory fallback, the queued writer, the entry format, retention and the null scope.Program.cs— creates that provider fromLogging:File:DirectoryandLogging:File:RetainDays, seeds its default levels ahead of the configured ones, writes the startup summary right after the host is built, hooks unhandled and unobserved exceptions, maps the native core's lines ontoHi.Nativelevels, and flushes and shuts the writer down around the native teardown.appsettings.json— the shippedLogging:Filesection: its levels, directory and retention.appsettings.Development.json— the Development overlay'sLogging:File:LogLevelkeys that keep the development-time browser-refresh middleware out of the file.Execution/ExecutionStatusService.cs— the engine-message lines under the fourHi.Engine.*categories, their per-id cap, and the run-start and run-end lines.Common/AuthController.cs— the sign-in, refused sign-in and sign-out lines.
See Also
- Main Panel — the menu bar whose Help dropdown holds 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
- Program and Hosting — the host that registers the file provider, seeds its levels and reads its
Logging:Filekeys