Add log sending
This commit is contained in:
parent
2d89881aac
commit
db68aa4a68
8 changed files with 131 additions and 13 deletions
1
.env
1
.env
|
|
@ -9,6 +9,7 @@ VITE_PLATFORM_PRIVACY=https://flotilla.social/privacy
|
||||||
VITE_PLATFORM_NAME=Flotilla
|
VITE_PLATFORM_NAME=Flotilla
|
||||||
VITE_PLATFORM_LOGO=static/logo.png
|
VITE_PLATFORM_LOGO=static/logo.png
|
||||||
VITE_PLATFORM_RELAYS=
|
VITE_PLATFORM_RELAYS=
|
||||||
|
VITE_PLATFORM_LOGEE=be523f2d2255fa96b281233ba9df60d63ef74f874bea96651edd4e1f79785703
|
||||||
VITE_PLATFORM_ACCENT="#7161FF"
|
VITE_PLATFORM_ACCENT="#7161FF"
|
||||||
VITE_THEME="clay"
|
VITE_THEME="clay"
|
||||||
VITE_PLATFORM_DESCRIPTION="The chat app built for self-hosted communities."
|
VITE_PLATFORM_DESCRIPTION="The chat app built for self-hosted communities."
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ You can also optionally create an `.env.local` file and populate it with the fol
|
||||||
- `VITE_PLATFORM_DESCRIPTION` - A description of the app
|
- `VITE_PLATFORM_DESCRIPTION` - A description of the app
|
||||||
- `VITE_PLATFORM_TERMS` - URL to your terms of service page
|
- `VITE_PLATFORM_TERMS` - URL to your terms of service page
|
||||||
- `VITE_PLATFORM_PRIVACY` - URL to your privacy policy page
|
- `VITE_PLATFORM_PRIVACY` - URL to your privacy policy page
|
||||||
|
- `VITE_PLATFORM_LOGEE` - A hex pubkey which will receive logs users send from their privacy settings
|
||||||
|
|
||||||
**Platform mode**
|
**Platform mode**
|
||||||
- `VITE_PLATFORM_RELAYS` - A comma-separated list of relay urls that will make flotilla operate in "platform mode". Disables all space browse/add/select functionality and makes the first platform relay the home page.
|
- `VITE_PLATFORM_RELAYS` - A comma-separated list of relay urls that will make flotilla operate in "platform mode". Disables all space browse/add/select functionality and makes the first platform relay the home page.
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import {
|
||||||
Domain,
|
Domain,
|
||||||
FollowLists,
|
FollowLists,
|
||||||
Handles,
|
Handles,
|
||||||
|
Logger,
|
||||||
MessagingRelayLists,
|
MessagingRelayLists,
|
||||||
MuteLists,
|
MuteLists,
|
||||||
Network,
|
Network,
|
||||||
|
|
@ -142,6 +143,7 @@ export const deletes = usePlugin(Deletes)
|
||||||
export const domain = usePlugin(Domain)
|
export const domain = usePlugin(Domain)
|
||||||
export const followLists = usePlugin(FollowLists)
|
export const followLists = usePlugin(FollowLists)
|
||||||
export const handles = usePlugin(Handles)
|
export const handles = usePlugin(Handles)
|
||||||
|
export const logger = usePlugin(Logger)
|
||||||
export const messagingRelayLists = usePlugin(MessagingRelayLists)
|
export const messagingRelayLists = usePlugin(MessagingRelayLists)
|
||||||
export const muteLists = usePlugin(MuteLists)
|
export const muteLists = usePlugin(MuteLists)
|
||||||
export const network = usePlugin(Network)
|
export const network = usePlugin(Network)
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ export const PLATFORM_LOGO = import.meta.env.PROD
|
||||||
|
|
||||||
export const PLATFORM_NAME = env("VITE_PLATFORM_NAME")
|
export const PLATFORM_NAME = env("VITE_PLATFORM_NAME")
|
||||||
|
|
||||||
|
export const PLATFORM_LOGEE = env("VITE_PLATFORM_LOGEE")
|
||||||
|
|
||||||
export const PLATFORM_ACCENT = env("VITE_PLATFORM_ACCENT")
|
export const PLATFORM_ACCENT = env("VITE_PLATFORM_ACCENT")
|
||||||
|
|
||||||
// components visual preset (see src/lib/components/theme.css). Selected per
|
// components visual preset (see src/lib/components/theme.css). Selected per
|
||||||
|
|
|
||||||
86
src/app/logger.ts
Normal file
86
src/app/logger.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
import type {Unsubscriber} from "svelte/store"
|
||||||
|
import {Capacitor} from "@capacitor/core"
|
||||||
|
import {tryCatch} from "@welshman/lib"
|
||||||
|
import type {Maybe} from "@welshman/lib"
|
||||||
|
import {DirectMessage} from "@welshman/domain"
|
||||||
|
import type {Logger} from "@welshman/app"
|
||||||
|
import {logger, wraps, writer} from "@app/core"
|
||||||
|
import {PLATFORM_LOGEE} from "@app/env"
|
||||||
|
|
||||||
|
const formatValue = (value: unknown) => {
|
||||||
|
if (value instanceof Error) return value.stack || value.message
|
||||||
|
if (typeof value === "string") return value
|
||||||
|
|
||||||
|
return tryCatch(() => JSON.stringify(value)) || String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bindLogging = ($logger: Logger) => {
|
||||||
|
const {error} = console
|
||||||
|
|
||||||
|
const log = (source: string, value: unknown) => $logger.log(source, {message: formatValue(value)})
|
||||||
|
|
||||||
|
const onError = (event: ErrorEvent) => log("error", event.error || event.message)
|
||||||
|
|
||||||
|
const onUnhandledRejection = (event: PromiseRejectionEvent) => log("rejection", event.reason)
|
||||||
|
|
||||||
|
console.error = (...args: unknown[]) => {
|
||||||
|
log("console", args.map(formatValue).join(" "))
|
||||||
|
error(...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
$logger.log("app", {
|
||||||
|
platform: Capacitor.getPlatform(),
|
||||||
|
build: import.meta.env.VITE_BUILD_HASH,
|
||||||
|
userAgent: navigator.userAgent,
|
||||||
|
})
|
||||||
|
|
||||||
|
window.addEventListener("error", onError)
|
||||||
|
window.addEventListener("unhandledrejection", onUnhandledRejection)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
console.error = error
|
||||||
|
window.removeEventListener("error", onError)
|
||||||
|
window.removeEventListener("unhandledrejection", onUnhandledRejection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errors are only useful after the fact if someone kept them around, so collect them into the
|
||||||
|
// app's logger, which the user can hand over via `sendLogs` when something goes wrong. Logging in
|
||||||
|
// swaps in a fresh app with an empty log, so re-bind against the new one when that happens.
|
||||||
|
export const setupLogging = () => {
|
||||||
|
let unbind: Maybe<Unsubscriber>
|
||||||
|
|
||||||
|
const unsubscribe = logger.subscribe($logger => {
|
||||||
|
unbind?.()
|
||||||
|
unbind = bindLogging($logger)
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unbind?.()
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sendLogs = async () => {
|
||||||
|
const messages = logger.get().messages.get()
|
||||||
|
const lines: string[] = []
|
||||||
|
let size = 0
|
||||||
|
|
||||||
|
// Relays commonly cap events at 64kb, so send only as much recent history as will fit
|
||||||
|
for (const message of messages.slice().reverse()) {
|
||||||
|
const line = JSON.stringify(message)
|
||||||
|
|
||||||
|
size += line.length
|
||||||
|
|
||||||
|
if (size > 32_000) break
|
||||||
|
|
||||||
|
lines.unshift(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await writer(DirectMessage)
|
||||||
|
.setContent(lines.join("\n"))
|
||||||
|
.addRecipient(PLATFORM_LOGEE)
|
||||||
|
.renderTemplate()
|
||||||
|
|
||||||
|
return wraps.get().publish({event, recipients: [PLATFORM_LOGEE], pow: 16})
|
||||||
|
}
|
||||||
|
|
@ -26,7 +26,6 @@ export type SettingsValues = {
|
||||||
hide_sensitive: boolean
|
hide_sensitive: boolean
|
||||||
trusted_relays: string[]
|
trusted_relays: string[]
|
||||||
report_usage: boolean
|
report_usage: boolean
|
||||||
report_errors: boolean
|
|
||||||
relay_auth: RelayAuthMode
|
relay_auth: RelayAuthMode
|
||||||
send_delay: number
|
send_delay: number
|
||||||
font_size: number
|
font_size: number
|
||||||
|
|
@ -39,7 +38,6 @@ export const defaultSettings: SettingsValues = {
|
||||||
hide_sensitive: true,
|
hide_sensitive: true,
|
||||||
trusted_relays: [],
|
trusted_relays: [],
|
||||||
report_usage: true,
|
report_usage: true,
|
||||||
report_errors: true,
|
|
||||||
relay_auth: RelayAuthMode.Conservative,
|
relay_auth: RelayAuthMode.Conservative,
|
||||||
send_delay: 0,
|
send_delay: 0,
|
||||||
font_size: 1.1,
|
font_size: 1.1,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
import {app} from "@app/core"
|
import {app} from "@app/core"
|
||||||
import {setupHistory} from "@app/routes"
|
import {setupHistory} from "@app/routes"
|
||||||
import {setupAnalytics} from "@app/analytics"
|
import {setupAnalytics} from "@app/analytics"
|
||||||
|
import {setupLogging} from "@app/logger"
|
||||||
import "@app/policies"
|
import "@app/policies"
|
||||||
import {restoreSession} from "@app/session"
|
import {restoreSession} from "@app/session"
|
||||||
import {signerRequests} from "@app/signer"
|
import {signerRequests} from "@app/signer"
|
||||||
|
|
@ -128,6 +129,9 @@
|
||||||
// Attach the user before anything reads or decrypts on their behalf
|
// Attach the user before anything reads or decrypts on their behalf
|
||||||
unsubscribers.push(await restoreSession())
|
unsubscribers.push(await restoreSession())
|
||||||
|
|
||||||
|
// Set up logging
|
||||||
|
unsubscribers.push(setupLogging())
|
||||||
|
|
||||||
// Sync stuff to storage
|
// Sync stuff to storage
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
sync({
|
sync({
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,33 @@
|
||||||
import Card from "@lib/components/Card.svelte"
|
import Card from "@lib/components/Card.svelte"
|
||||||
import PageContent from "@lib/components/PageContent.svelte"
|
import PageContent from "@lib/components/PageContent.svelte"
|
||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import Spinner from "@lib/components/Spinner.svelte"
|
||||||
import {pushToast} from "@app/toast"
|
import {pushToast} from "@app/toast"
|
||||||
import {PLATFORM_NAME} from "@app/env"
|
import {PLATFORM_NAME} from "@app/env"
|
||||||
|
import {sendLogs} from "@app/logger"
|
||||||
import {RelayAuthMode, userSettingsValues, publishSettings} from "@app/settings"
|
import {RelayAuthMode, userSettingsValues, publishSettings} from "@app/settings"
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
settings = {...$userSettingsValues}
|
settings = {...$userSettingsValues}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const send = async () => {
|
||||||
|
loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const thunk = await sendLogs()
|
||||||
|
const error = await thunk.waitForError()
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
pushToast({theme: "error", message: error})
|
||||||
|
} else {
|
||||||
|
pushToast({message: "Your logs have been sent. Thank you!"})
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onAuthModeChange = (checked: boolean) => {
|
const onAuthModeChange = (checked: boolean) => {
|
||||||
settings.relay_auth = checked ? RelayAuthMode.Aggressive : RelayAuthMode.Conservative
|
settings.relay_auth = checked ? RelayAuthMode.Aggressive : RelayAuthMode.Conservative
|
||||||
}
|
}
|
||||||
|
|
@ -26,6 +45,7 @@
|
||||||
})
|
})
|
||||||
|
|
||||||
let settings = $state({...$userSettingsValues})
|
let settings = $state({...$userSettingsValues})
|
||||||
|
let loading = $state(false)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form {onsubmit}>
|
<form {onsubmit}>
|
||||||
|
|
@ -48,17 +68,6 @@
|
||||||
<p>Controls whether {PLATFORM_NAME} will identify you to relays not in your lists.</p>
|
<p>Controls whether {PLATFORM_NAME} will identify you to relays not in your lists.</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
<FieldInline>
|
|
||||||
{#snippet label()}
|
|
||||||
<p>Report errors?</p>
|
|
||||||
{/snippet}
|
|
||||||
{#snippet input()}
|
|
||||||
<ToggleInput bind:checked={settings.report_errors} />
|
|
||||||
{/snippet}
|
|
||||||
{#snippet info()}
|
|
||||||
<p>Allow {PLATFORM_NAME} to send error reports to help improve the app.</p>
|
|
||||||
{/snippet}
|
|
||||||
</FieldInline>
|
|
||||||
<FieldInline>
|
<FieldInline>
|
||||||
{#snippet label()}
|
{#snippet label()}
|
||||||
<p>Report usage?</p>
|
<p>Report usage?</p>
|
||||||
|
|
@ -70,6 +79,21 @@
|
||||||
<p>Allow {PLATFORM_NAME} to collect anonymous usage data.</p>
|
<p>Allow {PLATFORM_NAME} to collect anonymous usage data.</p>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</FieldInline>
|
</FieldInline>
|
||||||
|
<FieldInline>
|
||||||
|
{#snippet label()}
|
||||||
|
<p>Something went wrong?</p>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet input()}
|
||||||
|
<Button class="button button-neutral" onclick={send} disabled={loading}>
|
||||||
|
<Spinner {loading}>Send Logs</Spinner>
|
||||||
|
</Button>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet info()}
|
||||||
|
<p>
|
||||||
|
Send a record of recent app activity to the {PLATFORM_NAME} team as a direct message.
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
|
</FieldInline>
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
|
<Card class="sticky -bottom-3 shadow-md flex flex-row items-center justify-between gap-4">
|
||||||
<Button class="button button-neutral" onclick={reset}>Discard Changes</Button>
|
<Button class="button button-neutral" onclick={reset}>Discard Changes</Button>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue