Skip to content

Conversation

myieye
Copy link
Collaborator

@myieye myieye commented Sep 12, 2025

The big win here is in the PR title: currently JS errors don't get further than the browser console.
This PR changes that so that our error handler also forwards global uncaught errors to our dotnet logging.
App errors and the log file:
image

I also made our notifications and copy-to-clipboard more robust.
Currently we only show (and copy-to-clipboard) a stack trace for dotnet exceptions.
Now we also do that for js errors as well.
Storybook notifications and the copied-to-clipboard text:
image

One problem is that dotnet errors turn into JS errors and so now we're logging them as JS error to dotnet a second time.
I'm not sure how to confidently avoid that. I'd rather have duplicate dotnet error logs than accidentally lose a JS error log.

UPDATE: Actually logging them again from JS actually has some subtle advantages:

  • It includes a slightly longer stack-trace (might no be useful)
  • I don't see all the dotnet exceptions that I trigger on the sandbox page in the log file 🤷

Copy link

coderabbitai bot commented Sep 12, 2025

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Introduces a JS-invokable .NET logger service (JsInvokableLogger) across backend and frontend: DI registration, provider wiring, TypeGen updates, generated TS enums/interfaces, service registry mapping and accessor, global error handling refactor to use the logger, and a minor proxy guard change in the DotNet service provider.

Changes

Cohort / File(s) Summary
Backend DI and provider wiring
backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs, backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs
Registers JsInvokableLogger as scoped; adds it to ExportedServices and DotnetService enum; maps enum to implementation in provider.
Backend new service
backend/FwLite/FwLiteShared/Services/JsInvokableLogger.cs
Adds public JsInvokableLogger class with [JSInvokable] Log(LogLevel, string) method forwarding to ILogger.
Type generation config
backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs
Exports Microsoft.Extensions.Logging.LogLevel enum to TypeScript (numeric).
Frontend generated types
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/DotnetService.ts, frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IJsInvokableLogger.ts, frontend/viewer/src/lib/dotnet-types/generated-types/Microsoft/Extensions/Logging/LogLevel.ts
Adds DotnetService.JsInvokableLogger; introduces IJsInvokableLogger with log(level, message); adds LogLevel enum with numeric values.
Frontend service registry and accessor
frontend/viewer/src/lib/services/service-provider.ts, frontend/viewer/src/lib/services/js-invokable-logger.ts
Extends LexboxServiceRegistry to include JsInvokableLogger; adds useJsInvokableLogger() helper to fetch the service.
DotNet proxy tweak
frontend/viewer/src/lib/services/service-provider-dotnet.ts
Updates proxy getter: removes toJSON guard; adds guard to ignore then (prevents promise-like behavior).
Global error handling
frontend/viewer/src/lib/errors/global-errors.ts
Refactors to unified error event handling; adds structured logging to .NET via JsInvokableLogger with retries and loop guard; centralizes detail extraction and notifications.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Suggested labels

💻 FW Lite

Suggested reviewers

  • hahn-kev

Poem

I thump the logs with cottoned feet,
New tunnels link our burrows neat. 🐇
Errors hop in—now neatly penned,
Across the wire, their stories send.
A carrot for each tidy trace,
JsInvoked, we log with grace.
Hippity-hop—ship in place!

✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch log-js-error-to-dotnet

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added the 💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related label Sep 12, 2025
Copy link

github-actions bot commented Sep 12, 2025

UI unit Tests

  1 files  ±0   45 suites  ±0   29s ⏱️ +2s
110 tests ±0  110 ✅ ±0  0 💤 ±0  0 ❌ ±0 
158 runs  ±0  158 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 1e0122e. ± Comparison against base commit 40c2ea9.

♻️ This comment has been updated with latest results.

Copy link

github-actions bot commented Sep 12, 2025

C# Unit Tests

130 tests   130 ✅  19s ⏱️
 20 suites    0 💤
  1 files      0 ❌

Results for commit 1e0122e.

♻️ This comment has been updated with latest results.

Copy link

argos-ci bot commented Sep 12, 2025

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - Sep 15, 2025, 9:16 AM

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (7)
frontend/viewer/src/lib/services/service-provider-dotnet.ts (2)

66-70: Reduce noisy debug logs in production.

Consider gating console.debug behind a dev guard to avoid log noise in production.

-        console.debug(`[Dotnet Proxy] Calling ${serviceName} method ${dotnetMethodName}`, args);
+        if (import.meta?.env?.DEV) console.debug(`[Dotnet Proxy] Calling ${serviceName} method ${dotnetMethodName}`, args);
...
-        console.debug(`[Dotnet Proxy] ${serviceName} method ${dotnetMethodName} returned`, result);
+        if (import.meta?.env?.DEV) console.debug(`[Dotnet Proxy] ${serviceName} method ${dotnetMethodName} returned`, result);

60-63: Nit: comment wording.

Minor wording tweak for clarity: “RuneD resource” → “Runed resource” or name the lib explicitly if that’s intended.

backend/FwLite/FwLiteShared/Services/JsInvokableLogger.cs (1)

8-13: Consider structured logging and basic scrubbing.

Current API accepts a single message string. If practical later, pass structured fields (message, stack, url, userAgent) and log with named properties; also scrub sensitive query params before logging.

Would you like me to sketch a backward-compatible DTO and JS usage that preserves this method but adds an optional LogContext payload?

frontend/viewer/src/lib/services/service-provider.ts (1)

43-54: Provide a safe no-op default for the logger (optional).

Like JsEventListener, consider a default to avoid throw when backend isn’t connected (storybook/SSR).

   private services: Partial<LexboxServiceRegistry> = {
     [DotnetService.JsEventListener]: {
       nextEventAsync(): Promise<IFwEvent> {
         console.warn('using default JsEvent listener which does nothing');
         return new Promise<IFwEvent>(() => {});
       },
       lastEvent(): Promise<IFwEvent | null> {
         console.warn('using default JsEvent listener which does nothing');
         return Promise.resolve(null);
       }
     }
+    ,
+    [DotnetService.JsInvokableLogger]: {
+      async log() {
+        // default no-op logger for environments without a backend
+      }
+    } as IJsInvokableLogger
   };
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/DotnetService.ts (1)

19-21: Small consistency nit: trailing comma.

Consider keeping a trailing comma after the last enum member to reduce diff noise on future insertions (if your TypeGen allows it).

frontend/viewer/src/lib/errors/global-errors.ts (2)

92-104: Enrich log payload with minimal context (URL, UA, timestamp) and better cause handling

Improves diagnostics without affecting UX.

Apply this diff:

 function getErrorString(event: UnifiedErrorEvent) {
   const details = [`Message: ${event.message}`];
   if (event.at) details.push(`at ${event.at}`);
   if (event.error instanceof Error) {
     const error: Error = event.error;
     if (error.stack) details.push(`Stack: ${error.stack}`);
-    if (error.cause) details.push(`Cause: ${tryStringify(error.cause)}`);
+    if (error.cause) {
+      if (error.cause instanceof Error) {
+        details.push(`Cause: ${error.cause.stack ?? error.cause.message}`);
+      } else {
+        details.push(`Cause: ${tryStringify(error.cause)}`);
+      }
+    }
   } else if (event.error) {
     details.push(`Error: ${tryStringify(event.error)}`);
   }
 
-  return details.join('\n');
+  // minimal environment context
+  try {
+    details.push(`URL: ${location.href}`);
+    details.push(`UserAgent: ${navigator.userAgent}`);
+    details.push(`Time: ${new Date().toISOString()}`);
+  } catch { /* ignore */ }
+  return details.join('\n');
 }

106-112: Make tryStringify always return a string

Prevents accidental “undefined” literals and improves readability of logs.

Apply this diff:

-function tryStringify(value: unknown): string | undefined {
+function tryStringify(value: unknown): string {
   try {
-    return JSON.stringify(value);
+    const s = JSON.stringify(value);
+    return s ?? String(value);
   } catch {
     return '(failed-to-stringify)';
   }
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bf7e37 and 6f98e41.

📒 Files selected for processing (11)
  • backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs (1 hunks)
  • backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs (3 hunks)
  • backend/FwLite/FwLiteShared/Services/JsInvokableLogger.cs (1 hunks)
  • backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs (2 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/DotnetService.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IJsInvokableLogger.ts (1 hunks)
  • frontend/viewer/src/lib/dotnet-types/generated-types/Microsoft/Extensions/Logging/LogLevel.ts (1 hunks)
  • frontend/viewer/src/lib/errors/global-errors.ts (2 hunks)
  • frontend/viewer/src/lib/services/js-invokable-logger.ts (1 hunks)
  • frontend/viewer/src/lib/services/service-provider-dotnet.ts (1 hunks)
  • frontend/viewer/src/lib/services/service-provider.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-22T09:19:37.386Z
Learnt from: rmunn
PR: sillsdev/languageforge-lexbox#1836
File: frontend/viewer/src/lib/components/audio/AudioDialog.svelte:25-25
Timestamp: 2025-07-22T09:19:37.386Z
Learning: In the sillsdev/languageforge-lexbox project, when file size limits or other constants need to be shared between C# backend and TypeScript frontend code, prefer exposing them through Reinforced.Typings type generation rather than hardcoding the values separately. This ensures consistency and prevents discrepancies when values change.

Applied to files:

  • backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs
🧬 Code graph analysis (9)
backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs (1)
backend/FwLite/FwLiteShared/Services/JsInvokableLogger.cs (1)
  • JsInvokableLogger (6-14)
backend/FwLite/FwLiteShared/Services/JsInvokableLogger.cs (2)
backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs (1)
  • Task (68-83)
backend/FwLite/FwLiteShared/Auth/LoggerAdapter.cs (2)
  • Log (26-29)
  • LogLevel (8-20)
backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs (1)
backend/FwLite/FwLiteShared/Auth/LoggerAdapter.cs (1)
  • LogLevel (8-20)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IJsInvokableLogger.ts (1)
backend/FwLite/FwLiteShared/Auth/LoggerAdapter.cs (1)
  • LogLevel (8-20)
frontend/viewer/src/lib/services/js-invokable-logger.ts (1)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IJsInvokableLogger.ts (1)
  • IJsInvokableLogger (8-11)
backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs (1)
backend/FwLite/FwLiteShared/Services/JsInvokableLogger.cs (1)
  • JsInvokableLogger (6-14)
frontend/viewer/src/lib/services/service-provider.ts (1)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IJsInvokableLogger.ts (1)
  • IJsInvokableLogger (8-11)
frontend/viewer/src/lib/errors/global-errors.ts (3)
frontend/viewer/src/lib/notifications/notifications.ts (2)
  • AppNotification (39-97)
  • error (52-67)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IJsInvokableLogger.ts (1)
  • IJsInvokableLogger (8-11)
frontend/viewer/src/lib/services/js-invokable-logger.ts (1)
  • useJsInvokableLogger (4-6)
frontend/viewer/src/lib/dotnet-types/generated-types/Microsoft/Extensions/Logging/LogLevel.ts (1)
backend/FwLite/FwLiteShared/Auth/LoggerAdapter.cs (1)
  • LogLevel (8-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Build API / publish-api
  • GitHub Check: Build UI / publish-ui
  • GitHub Check: check-and-lint
  • GitHub Check: Build FW Lite and run tests
  • GitHub Check: frontend-component-unit-tests
  • GitHub Check: frontend
  • GitHub Check: Analyze (csharp)
🔇 Additional comments (14)
frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/DotnetService.ts (1)

19-21: Enum member addition aligns with backend service exposure.

JsInvokableLogger is correctly added after JsEventListener; generated string values match backend enum names.

frontend/viewer/src/lib/services/service-provider-dotnet.ts (1)

62-64: Good guard to prevent the proxy from being treated as a Promise.

Returning undefined for "then" avoids accidental thenable detection on proxied .NET objects.

backend/FwLite/FwLiteShared/TypeGen/ReinforcedFwLiteTypingConfig.cs (2)

29-29: Required import for LogLevel export.

Adding Microsoft.Extensions.Logging is necessary for the new enum export.


175-177: Exporting LogLevel as numeric enum is correct.

UseString(false) matches .NET numeric values (Trace=0 … None=6) and aligns with generated TS.

backend/FwLite/FwLiteShared/FwLiteSharedKernel.cs (1)

39-41: Scoped lifetime is appropriate for JS-invokable services.

Per-circuit scope prevents cross-circuit leakage and matches JsEventListener.

backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs (4)

37-38: Expose JsInvokableLogger to the front end.

Included in ExportedServices so it’s delivered to JS—good.


55-56: Service type mapping added.

Mapping DotnetService.JsInvokableLogger → JsInvokableLogger is correct.


112-114: Backend enum stays in sync with generated TS.

New members appended; JSON string enum converter keeps names stable for interop.


68-83: Ensure DotNetObjectReference returned by SetService is disposed

Repo search found only the method definition (backend/FwLite/FwLiteShared/Services/FwLiteProvider.cs:68–83) and no callers; verify every caller disposes the returned IDisposable, or make SetService/Provider own/track/dispose the DotNetObjectReference to avoid leaks across page reloads.

frontend/viewer/src/lib/services/service-provider.ts (3)

20-20: Type import added for logger interface.

Keeps registry strongly typed.


37-38: Registry mapping for JsInvokableLogger added.

Front-end can now resolve the logger via DotnetService key.


66-75: Prefer tryGet when accessing optional services.

useJsInvokableLogger already returns ServiceProvider.tryGetService(DotnetService.JsInvokableLogger). The global error handler defines tryGetLogger (with retries) but there is an unguarded call to await logger.log(...); confirm frontend/viewer/src/lib/errors/global-errors.ts guards that call (check for undefined / return early) before invoking logger.log.

frontend/viewer/src/lib/dotnet-types/generated-types/Microsoft/Extensions/Logging/LogLevel.ts (1)

6-14: Numeric enum values match .NET’s LogLevel.

Trace=0 … None=6 matches backend; generation looks correct.

frontend/viewer/src/lib/dotnet-types/generated-types/FwLiteShared/Services/IJsInvokableLogger.ts (1)

6-11: Generated interface looks correct and matches intended surface

Type-only import, enum usage, and Promise-returning log are consistent with the rest of the PR.

@myieye myieye force-pushed the log-js-error-to-dotnet branch from d0d9a24 to 1e0122e Compare September 15, 2025 09:14
@myieye myieye merged commit 5e5848b into develop Sep 15, 2025
27 checks passed
@myieye myieye deleted the log-js-error-to-dotnet branch September 15, 2025 09:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant