Implement logging system

This commit is contained in:
Paul Frazee 2023-01-02 17:38:13 -06:00
parent 99cec71ed7
commit f6a0e634d7
39 changed files with 442 additions and 125 deletions

View file

@ -24,19 +24,19 @@ export async function setupState() {
try {
data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {}
rootStore.hydrate(data)
} catch (e) {
console.error('Failed to load state from storage', e)
} catch (e: any) {
rootStore.log.error('Failed to load state from storage', e.toString())
}
console.log('Initial hydrate', rootStore.me)
rootStore.log.debug('Initial hydrate')
rootStore.session
.connect()
.then(() => {
console.log('Session connected', rootStore.me)
rootStore.log.debug('Session connected')
return rootStore.fetchStateUpdate()
})
.catch(e => {
console.log('Failed initial connect', e)
.catch((e: any) => {
rootStore.log.warn('Failed initial connect', e.toString())
})
// @ts-ignore .on() is correct -prf
api.sessionManager.on('session', () => {

View file

@ -99,7 +99,7 @@ export async function post(
) {
encoding = 'image/jpeg'
} else {
console.error(
store.log.warn(
'Unexpected image format for thumbnail, skipping',
thumbLocal.uri,
)
@ -126,7 +126,10 @@ export async function post(
},
} as AppBskyEmbedExternal.Main
} catch (e: any) {
console.error('Failed to fetch link meta', link.value, e)
store.log.warn(
`Failed to fetch link meta for ${link.value}`,
e.toString(),
)
}
}
}

View file

@ -405,7 +405,6 @@ export class FeedModel {
cursor = this.feed[res.data.feed.length - 1]
? ts(this.feed[res.data.feed.length - 1])
: undefined
console.log(numToFetch, cursor, res.data.feed.length)
} while (numToFetch > 0)
this._xIdle()
} catch (e: any) {

94
src/state/models/log.ts Normal file
View file

@ -0,0 +1,94 @@
import {makeAutoObservable} from 'mobx'
import {isObj, hasProp} from '../lib/type-guards'
interface LogEntry {
id: string
type?: string
summary?: string
details?: string
ts?: number
}
let _lastTs: string
let _lastId: string
function genId(): string {
let candidate = String(Date.now())
if (_lastTs === candidate) {
const id = _lastId + 'x'
_lastId = id
return id
}
_lastTs = candidate
_lastId = candidate
return candidate
}
export class LogModel {
entries: LogEntry[] = []
constructor() {
makeAutoObservable(this, {serialize: false, hydrate: false})
}
serialize(): unknown {
return {
entries: this.entries.slice(-100),
}
}
hydrate(v: unknown) {
if (isObj(v)) {
if (hasProp(v, 'entries') && Array.isArray(v.entries)) {
this.entries = v.entries.filter(
e => isObj(e) && hasProp(e, 'id') && typeof e.id === 'string',
)
}
}
}
private add(entry: LogEntry) {
this.entries.push(entry)
}
debug(summary: string, details?: any) {
if (details && typeof details !== 'string') {
details = JSON.stringify(details, null, 2)
}
console.debug(summary, details || '')
this.add({
id: genId(),
type: 'debug',
summary,
details,
ts: Date.now(),
})
}
warn(summary: string, details?: any) {
if (details && typeof details !== 'string') {
details = JSON.stringify(details, null, 2)
}
console.warn(summary, details || '')
this.add({
id: genId(),
type: 'warn',
summary,
details,
ts: Date.now(),
})
}
error(summary: string, details?: any) {
if (details && typeof details !== 'string') {
details = JSON.stringify(details, null, 2)
}
console.error(summary, details || '')
this.add({
id: genId(),
type: 'error',
summary,
details,
ts: Date.now(),
})
}
}

View file

@ -104,13 +104,22 @@ export class MeModel {
})
await Promise.all([
this.memberships?.setup().catch(e => {
console.error('Failed to setup memberships model', e)
this.rootStore.log.error(
'Failed to setup memberships model',
e.toString(),
)
}),
this.mainFeed.setup().catch(e => {
console.error('Failed to setup main feed model', e)
this.rootStore.log.error(
'Failed to setup main feed model',
e.toString(),
)
}),
this.notifications.setup().catch(e => {
console.error('Failed to setup notifications model', e)
this.rootStore.log.error(
'Failed to setup notifications model',
e.toString(),
)
}),
])
} else {

View file

@ -149,7 +149,10 @@ export class NotificationsViewItemModel implements GroupedNotification {
depth: 0,
})
await this.additionalPost.setup().catch(e => {
console.error('Failed to load post needed by notification', e)
this.rootStore.log.error(
'Failed to load post needed by notification',
e.toString(),
)
})
}
}
@ -262,8 +265,11 @@ export class NotificationsViewModel {
seenAt: new Date().toISOString(),
})
this.rootStore.me.clearNotificationCount()
} catch (e) {
console.log('Failed to update notifications read state', e)
} catch (e: any) {
this.rootStore.log.warn(
'Failed to update notifications read state',
e.toString(),
)
}
}
@ -350,7 +356,6 @@ export class NotificationsViewModel {
this._updateAll(res)
numToFetch -= res.data.notifications.length
cursor = this.notifications[res.data.notifications.length - 1].indexedAt
console.log(numToFetch, cursor, res.data.notifications.length)
} while (numToFetch > 0)
this._xIdle()
} catch (e: any) {
@ -379,9 +384,9 @@ export class NotificationsViewModel {
itemModels.push(itemModel)
}
await Promise.all(promises).catch(e => {
console.error(
this.rootStore.log.error(
'Uncaught failure during notifications-view _appendAll()',
e,
e.toString(),
)
})
runInAction(() => {

View file

@ -114,20 +114,28 @@ export class ProfileUiModel {
await Promise.all([
this.profile
.setup()
.catch(err => console.error('Failed to fetch profile', err)),
.catch(err =>
this.rootStore.log.error('Failed to fetch profile', err.toString()),
),
this.feed
.setup()
.catch(err => console.error('Failed to fetch feed', err)),
.catch(err =>
this.rootStore.log.error('Failed to fetch feed', err.toString()),
),
])
if (this.isUser) {
await this.memberships
.setup()
.catch(err => console.error('Failed to fetch members', err))
.catch(err =>
this.rootStore.log.error('Failed to fetch members', err.toString()),
)
}
if (this.isScene) {
await this.members
.setup()
.catch(err => console.error('Failed to fetch members', err))
.catch(err =>
this.rootStore.log.error('Failed to fetch members', err.toString()),
)
}
}

View file

@ -203,7 +203,6 @@ export class ProfileViewModel {
}
private _replaceAll(res: GetProfile.Response) {
console.log(res.data)
this.did = res.data.did
this.handle = res.data.handle
Object.assign(this.declaration, res.data.declaration)

View file

@ -6,6 +6,7 @@ import {makeAutoObservable} from 'mobx'
import {sessionClient as AtpApi, SessionServiceClient} from '@atproto/api'
import {createContext, useContext} from 'react'
import {isObj, hasProp} from '../lib/type-guards'
import {LogModel} from './log'
import {SessionModel} from './session'
import {NavigationModel} from './navigation'
import {ShellUiModel} from './shell-ui'
@ -16,6 +17,7 @@ import {OnboardModel} from './onboard'
import {isNetworkError} from '../../lib/errors'
export class RootStoreModel {
log = new LogModel()
session = new SessionModel(this)
nav = new NavigationModel()
shell = new ShellUiModel()
@ -53,16 +55,17 @@ export class RootStoreModel {
await this.session.connect()
}
await this.me.fetchStateUpdate()
} catch (e: unknown) {
} catch (e: any) {
if (isNetworkError(e)) {
this.session.setOnline(false) // connection lost
}
console.error('Failed to fetch latest state', e)
this.log.error('Failed to fetch latest state', e.toString())
}
}
serialize(): unknown {
return {
log: this.log.serialize(),
session: this.session.serialize(),
me: this.me.serialize(),
nav: this.nav.serialize(),
@ -73,8 +76,8 @@ export class RootStoreModel {
hydrate(v: unknown) {
if (isObj(v)) {
if (hasProp(v, 'session')) {
this.session.hydrate(v.session)
if (hasProp(v, 'log')) {
this.log.hydrate(v.log)
}
if (hasProp(v, 'me')) {
this.me.hydrate(v.me)

View file

@ -121,11 +121,11 @@ export class SessionModel {
try {
const serviceUri = new URL(this.data.service)
this.rootStore.api.xrpc.uri = serviceUri
} catch (e) {
console.error(
} catch (e: any) {
this.rootStore.log.error(
`Invalid service URL: ${this.data.service}. Resetting session.`,
e.toString(),
)
console.error(e)
this.clear()
return false
}
@ -160,7 +160,10 @@ export class SessionModel {
this.rootStore.me.clear()
}
this.rootStore.me.load().catch(e => {
console.error('Failed to fetch local user information', e)
this.rootStore.log.error(
'Failed to fetch local user information',
e.toString(),
)
})
return // success
}
@ -204,7 +207,10 @@ export class SessionModel {
this.configureApi()
this.setOnline(true, false)
this.rootStore.me.load().catch(e => {
console.error('Failed to fetch local user information', e)
this.rootStore.log.error(
'Failed to fetch local user information',
e.toString(),
)
})
}
}
@ -240,7 +246,10 @@ export class SessionModel {
this.rootStore.onboard.start()
this.configureApi()
this.rootStore.me.load().catch(e => {
console.error('Failed to fetch local user information', e)
this.rootStore.log.error(
'Failed to fetch local user information',
e.toString(),
)
})
}
}
@ -248,7 +257,10 @@ export class SessionModel {
async logout() {
if (this.hasSession) {
this.rootStore.api.com.atproto.session.delete().catch((e: any) => {
console.error('(Minor issue) Failed to delete session on the server', e)
this.rootStore.log.warn(
'(Minor issue) Failed to delete session on the server',
e,
)
})
}
this.rootStore.clearAll()

View file

@ -98,8 +98,11 @@ export class SuggestedInvitesView {
try {
// TODO need to fetch all!
await this.sceneAssertionsView.setup()
} catch (e) {
console.error(e)
} catch (e: any) {
this.rootStore.log.error(
'Failed to fetch current scene members in suggested invites',
e.toString(),
)
this._xIdle(
'Failed to fetch the current scene members. Check your internet connection and try again.',
)
@ -107,8 +110,11 @@ export class SuggestedInvitesView {
}
try {
await this.myFollowsView.setup()
} catch (e) {
console.error(e)
} catch (e: any) {
this.rootStore.log.error(
'Failed to fetch current followers in suggested invites',
e.toString(),
)
this._xIdle(
'Failed to fetch the your current followers. Check your internet connection and try again.',
)