Archived
319 lines
20 KiB
HTML
319 lines
20 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Log Viewer Page | HiAPI-C# 2025 </title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="title" content="Log Viewer Page | HiAPI-C# 2025 ">
|
|
|
|
|
|
<link rel="icon" href="../../img/HiAPI.favicon.ico">
|
|
<link rel="stylesheet" href="../../public/docfx.min.css">
|
|
<link rel="stylesheet" href="../../public/main.css">
|
|
<meta name="docfx:navrel" content="../../toc.html">
|
|
<meta name="docfx:tocrel" content="toc.html">
|
|
|
|
<meta name="docfx:rel" content="../../">
|
|
|
|
|
|
|
|
<meta name="loc:inThisArticle" content="In this article">
|
|
<meta name="loc:searchResultsCount" content="{count} results for "{query}"">
|
|
<meta name="loc:searchNoResults" content="No results for "{query}"">
|
|
<meta name="loc:tocFilter" content="Filter by title">
|
|
<meta name="loc:nextArticle" content="Next">
|
|
<meta name="loc:prevArticle" content="Previous">
|
|
<meta name="loc:themeLight" content="Light">
|
|
<meta name="loc:themeDark" content="Dark">
|
|
<meta name="loc:themeAuto" content="Auto">
|
|
<meta name="loc:changeTheme" content="Change theme">
|
|
<meta name="loc:copy" content="Copy">
|
|
<meta name="loc:downloadPdf" content="Download PDF">
|
|
|
|
<script type="module" src="./../../public/docfx.min.js"></script>
|
|
|
|
<script>
|
|
const theme = localStorage.getItem('theme') || 'auto'
|
|
document.documentElement.setAttribute('data-bs-theme', theme === 'auto' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : theme)
|
|
</script>
|
|
|
|
</head>
|
|
|
|
<body class="tex2jax_ignore" data-layout="" data-yaml-mime="">
|
|
<header class="bg-body border-bottom">
|
|
<nav id="autocollapse" class="navbar navbar-expand-md" role="navigation">
|
|
<div class="container-xxl flex-nowrap">
|
|
<a class="navbar-brand" href="../../index.html">
|
|
<img id="logo" class="svg" src="../../img/HiAPI.logo.png" alt="">
|
|
|
|
</a>
|
|
<button class="btn btn-lg d-md-none border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navpanel" aria-controls="navpanel" aria-expanded="false" aria-label="Toggle navigation">
|
|
<i class="bi bi-three-dots"></i>
|
|
</button>
|
|
<div class="collapse navbar-collapse" id="navpanel">
|
|
<div id="navbar">
|
|
<form class="search" role="search" id="search">
|
|
<i class="bi bi-search"></i>
|
|
<input class="form-control" id="search-query" type="search" disabled placeholder="Search" autocomplete="off" aria-label="Search">
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="container-xxl">
|
|
<div class="toc-offcanvas">
|
|
<div class="offcanvas-md offcanvas-start" tabindex="-1" id="tocOffcanvas" aria-labelledby="tocOffcanvasLabel">
|
|
<div class="offcanvas-header">
|
|
<h5 class="offcanvas-title" id="tocOffcanvasLabel">Table of Contents</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#tocOffcanvas" aria-label="Close"></button>
|
|
</div>
|
|
<div class="offcanvas-body">
|
|
<nav class="toc" id="toc"></nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<div class="actionbar">
|
|
<button class="btn btn-lg border-0 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas" aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
|
|
<i class="bi bi-list"></i>
|
|
</button>
|
|
|
|
<nav id="breadcrumb"></nav>
|
|
</div>
|
|
|
|
<article data-uid="Anatomy-LogViewer">
|
|
<h1 id="log-viewer-page">Log Viewer Page</h1>
|
|
|
|
<p>The Log Viewer is the read-only screen at <code>/preference/log</code>, route name <code>preference-log</code>, that
|
|
renders the web service's own log file for the current day. It is not a Control-Tree page and takes
|
|
no <code>?tree=</code> 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 <code>/util/</code>.</p>
|
|
<h2 id="reaching-the-page">Reaching the Page</h2>
|
|
<p>The only navigation into the page is the <code>Show Log</code> 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 <code>article</code> icon with the tooltip <code>View the application log</code>. 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.</p>
|
|
<p>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 <code>Log Viewer - HiNC</code> and the menu bar's active-page
|
|
indicator reads <code>Log Viewer</code>.</p>
|
|
<h2 id="what-it-reads">What It Reads</h2>
|
|
<p>Two endpoints, both on the project controller, and both served from the same file:</p>
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Request</th>
|
|
<th>Answer</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td><code>GET /api/project/logs</code></td>
|
|
<td><code>{ date, content }</code> for today's file; <code>404</code> with a message when there is none</td>
|
|
</tr>
|
|
<tr>
|
|
<td><code>GET /api/project/download-log</code></td>
|
|
<td>the same file as a <code>text/plain</code> attachment; <code>404</code> with the same message when there is none</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<p>Both compose <code>logs/log-{yyyy-MM-dd}.txt</code> 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 <code>400</code> with the exception message.</p>
|
|
<p>The two client wrappers part company on that <code>404</code>. The wrapper over <code>logs</code> 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 — <code>404</code> included, every non-OK
|
|
status becomes that same typed error, a path the disabled-without-content <code>Download</code> 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.</p>
|
|
<p>The date badge shows whatever date the last answer reported. On the <code>404</code> 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.</p>
|
|
<h2 id="refresh-and-the-auto-interval">Refresh and the Auto Interval</h2>
|
|
<p>The page loads once on mount. After that every reload is driven by one of three things: the
|
|
<code>Refresh</code> button, the <code>Retry</code> button inside the error panel, or the auto-refresh timer.</p>
|
|
<p>Auto-refresh is off by default. The <code>Auto</code> toggle arms it and the interval select beside it chooses
|
|
the period from a fixed list of <code>2 s</code>, <code>5 s</code>, <code>10 s</code> and <code>30 s</code>, 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.</p>
|
|
<p>Ticks are not coalesced with a request already in flight — each tick starts the same load
|
|
unconditionally.</p>
|
|
<p>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.</p>
|
|
<h3 id="the-page-is-kept-alive">The Page Is Kept Alive</h3>
|
|
<p>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 <code>last loaded</code> stamp, the toggle and the chosen interval are all still there, and the
|
|
view refreshes on the next tick or on <code>Refresh</code>.</p>
|
|
<h2 id="errors-and-scroll-position">Errors and Scroll Position</h2>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<h2 id="level-filtering-and-where-the-file-comes-from">Level Filtering, and Where the File Comes From</h2>
|
|
<p>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 <a class="xref" href="program-and-hosting.html">Program and Hosting</a> for those rules and how
|
|
they differ from the console provider's.</p>
|
|
<p>The sink is a daily file logger provider registered at startup against a <code>logs</code> folder under the
|
|
process's current working directory. It writes one entry per line as
|
|
<code>[yyyy-MM-dd HH:mm:ss.fff] [Level] Category - message</code>, 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.</p>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<h2 id="copy-download-and-what-the-page-cannot-do">Copy, Download, and What the Page Cannot Do</h2>
|
|
<p><code>Copy</code> writes the loaded text to the system clipboard and reports the outcome as a toast, positive
|
|
or negative. <code>Download</code> fetches the file as a blob from the download endpoint and saves it
|
|
client-side under the name <code>log-<date>.txt</code>, repeating the date the last answer reported, and also
|
|
ends in a toast. Both buttons are disabled while there is nothing loaded.</p>
|
|
<p>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.</p>
|
|
<h2 id="layout">Layout</h2>
|
|
<ul>
|
|
<li>Log Viewer Page — a viewport-locked page sized from the layout's measured header and footer, so
|
|
the window itself never scrolls
|
|
<ul>
|
|
<li>Top Tool Bar
|
|
<ul>
|
|
<li><code>Log Viewer</code> Title</li>
|
|
<li>Date Badge — outlined; carries the date the last answer reported, and reads <code>today</code> only
|
|
before any answer has set one. Coloured as the primary accent while content is loaded and grey
|
|
otherwise</li>
|
|
<li>Last-Loaded Caption — <code>last loaded: {time}</code>, shown once a load has succeeded. The time is
|
|
formatted in the <strong>application</strong> 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</li>
|
|
<li>Line Count Caption — shown only while content is loaded, where it reads <code>{count} lines</code>. The
|
|
plural string's <code>no lines</code> and <code>one line</code> forms 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</li>
|
|
<li><code>Auto</code> Toggle — arms auto-refresh; off by default. Tooltip <code>Re-fetch the log file periodically</code></li>
|
|
<li>Interval Select — <code>2 s</code> / <code>5 s</code> / <code>10 s</code> / <code>30 s</code>, defaulting to <code>5 s</code>; disabled while <code>Auto</code>
|
|
is off</li>
|
|
<li><code>Refresh</code> Button — reloads at once, and shows a spinner in place of its label while the
|
|
request is in flight. Tooltip <code>Reload the log file</code></li>
|
|
<li><code>Copy</code> Button — disabled without content. Tooltip <code>Copy log content to clipboard</code></li>
|
|
<li><code>Download</code> Button — disabled without content. Tooltip <code>Download today's log file</code></li>
|
|
</ul>
|
|
</li>
|
|
<li>Separator</li>
|
|
<li>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:
|
|
<ul>
|
|
<li>Error Panel — an error icon, the failure message, and a <code>Retry</code> Button that repeats the load</li>
|
|
<li>Empty Panel — a document icon, <code>No log file for today.</code>, and the hint
|
|
<code>Log file will appear once the webservice writes its first entry.</code></li>
|
|
<li>Log Content — the file's text, preformatted and read-only, with no folding, no line numbers and
|
|
no per-line affordance of any kind</li>
|
|
<li>Loading Spinner — with <code>Loading log...</code> beneath it, shown while a load is in flight and
|
|
nothing has been loaded yet</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
</ul>
|
|
<h2 id="source-code-path">Source Code Path</h2>
|
|
<p>See <a class="xref" href="../index.html">HiNC App Anatomy</a> for git repository links.</p>
|
|
<h3 id="web-application">Web Application</h3>
|
|
<p>HiNC-2025-webservice (Quasar CLI SPA):</p>
|
|
<ul>
|
|
<li><code>wwwroot-src/src/pages/LogViewerPage.vue</code> — 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.</li>
|
|
<li><code>wwwroot-src/src/api/logs.ts</code> — the typed wrappers over the two endpoints, the <code>404</code>-to-empty
|
|
normalisation with its browser-clock date fallback, and the error type carrying the HTTP status.</li>
|
|
<li><code>wwwroot-src/src/router/routes.ts</code> — the <code>preference/log</code> path, the <code>preference-log</code> route name and
|
|
the title key the tab and the menu bar's active-page indicator resolve.</li>
|
|
<li><code>wwwroot-src/src/router/index.ts</code> — the login guard the route passes through, and the retitle hook
|
|
that names the browser tab.</li>
|
|
<li><code>wwwroot-src/src/components/AppMenuBar.vue</code> — the always-visible <code>Show Log</code> button in the bar's
|
|
right-hand group, and the Preference dropdown that carries no log entry.</li>
|
|
<li><code>wwwroot-src/src/layouts/MainLayout.vue</code> — the keep-alive that caches this page rather than
|
|
unmounting it, and the project epoch that rebuilds the cache.</li>
|
|
<li><code>wwwroot-src/src/stores/appState.ts</code> — the message action that forwards a load failure to the
|
|
footer's foreground channel.</li>
|
|
<li><code>wwwroot-src/src/stores/routineProgress.ts</code> — the foreground-message store that failure lands in.</li>
|
|
<li><code>wwwroot-src/src/utils/pageFit.ts</code> — the page-height function that locks the page to the measured
|
|
viewport so only the log area scrolls.</li>
|
|
<li><code>wwwroot-src/src/i18n/en/logs.ts</code> — the page's own English strings: the title, the empty and
|
|
loading states, the plural line count and the tooltips.</li>
|
|
<li><code>wwwroot-src/src/i18n/en/menu.ts</code> — the <code>Show Log</code> label and its tooltip.</li>
|
|
<li><code>wwwroot-src/src/i18n/en/common.ts</code> — the shared <code>Auto</code>, <code>Refresh</code>, <code>Copy</code> and <code>Download</code> labels.</li>
|
|
<li><code>wwwroot-src/src/i18n/en/routes.ts</code> — the <code>Log Viewer</code> route title.</li>
|
|
<li><code>Environments/ProjectController.cs</code> — the two endpoints the page reads, both resolving today's file
|
|
under the working directory, plus the append endpoint nothing calls.</li>
|
|
<li><code>Common/DailyFileLoggerProvider.cs</code> — the sink: the per-day file, the entry format, the lock, the
|
|
swallowed I/O failures and the null scope.</li>
|
|
<li><code>Program.cs</code> — registers that provider and its level filters, and writes the one startup entry
|
|
right after the host is built.</li>
|
|
</ul>
|
|
<h2 id="see-also">See Also</h2>
|
|
<ul>
|
|
<li><a class="xref" href="../shell/main-panel.html">Main Panel</a> — the menu bar whose right-hand <code>Show Log</code> button is the only navigation into this page</li>
|
|
<li><a class="xref" href="../shell/preference-menu.html">Preference Menu Dropdown</a> — the dropdown this route's path is named after, and which carries no log entry of its own</li>
|
|
</ul>
|
|
|
|
</article>
|
|
|
|
<div class="contribution d-print-none">
|
|
</div>
|
|
|
|
<div class="next-article d-print-none border-top" id="nextArticle"></div>
|
|
|
|
</div>
|
|
|
|
<div class="affix">
|
|
<nav id="affix"></nav>
|
|
</div>
|
|
</main>
|
|
|
|
<div class="container-xxl search-results" id="search-results"></div>
|
|
|
|
<footer class="border-top text-secondary">
|
|
<div class="container-xxl">
|
|
<div class="flex-fill">
|
|
<span> Copyright © 2025 <a href='https://superhightech.com.tw'>Tech Coordinate</a>. All rights reserved. <a href='https://superhightech.com.tw'>超級高科技股份有限公司</a> © 2025 版權所有 </span>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|