···178178 ) {
179179 encoding = 'image/jpeg'
180180 } else {
181181- store.log.warn(
182182- 'Unexpected image format for thumbnail, skipping',
183183- opts.extLink.localThumb.path,
184184- )
181181+ store.log.warn('Unexpected image format for thumbnail, skipping', {
182182+ thumbnail: opts.extLink.localThumb.path,
183183+ })
185184 }
186185 if (encoding) {
187186 const thumbUploadRes = await uploadBlob(
+2-2
src/lib/hooks/useFollowProfile.ts
···2222 following: false,
2323 }
2424 } catch (e: any) {
2525- store.log.error('Failed to delete follow', e)
2525+ store.log.error('Failed to delete follow', {error: e})
2626 throw e
2727 }
2828 } else if (state === FollowState.NotFollowing) {
···4040 following: true,
4141 }
4242 } catch (e: any) {
4343- store.log.error('Failed to create follow', e)
4343+ store.log.error('Failed to create follow', {error: e})
4444 throw e
4545 }
4646 }
+6-6
src/lib/hooks/useOTAUpdate.ts
···3434 // show a popup modal
3535 showUpdatePopup()
3636 } catch (e) {
3737- console.error('useOTAUpdate: Error while checking for update', e)
3838- store.log.error('useOTAUpdate: Error while checking for update', e)
3737+ store.log.error('useOTAUpdate: Error while checking for update', {
3838+ error: e,
3939+ })
3940 }
4041 }, [showUpdatePopup, store.log])
4142 const updateEventListener = useCallback(
4243 (event: Updates.UpdateEvent) => {
4344 store.log.debug('useOTAUpdate: Listening for update...')
4445 if (event.type === Updates.UpdateEventType.ERROR) {
4545- store.log.error(
4646- 'useOTAUpdate: Error while listening for update',
4747- event.message,
4848- )
4646+ store.log.error('useOTAUpdate: Error while listening for update', {
4747+ message: event.message,
4848+ })
4949 } else if (event.type === Updates.UpdateEventType.NO_UPDATE_AVAILABLE) {
5050 // Handle no update available
5151 // do nothing
+9-10
src/lib/notifications/notifications.ts
···3030 appId: 'xyz.blueskyweb.app',
3131 })
3232 store.log.debug('Notifications: Sent push token (init)', {
3333- type: token.type,
3333+ tokenType: token.type,
3434 token: token.data,
3535 })
3636 } catch (error) {
3737- store.log.error('Notifications: Failed to set push token', error)
3737+ store.log.error('Notifications: Failed to set push token', {error})
3838 }
3939 }
40404141 // listens for new changes to the push token
4242 // In rare situations, a push token may be changed by the push notification service while the app is running. When a token is rolled, the old one becomes invalid and sending notifications to it will fail. A push token listener will let you handle this situation gracefully by registering the new token with your backend right away.
4343 Notifications.addPushTokenListener(async ({data: t, type}) => {
4444- store.log.debug('Notifications: Push token changed', {t, type})
4444+ store.log.debug('Notifications: Push token changed', {t, tokenType: type})
4545 if (t) {
4646 try {
4747 await store.agent.api.app.bsky.notification.registerPush({
···5151 appId: 'xyz.blueskyweb.app',
5252 })
5353 store.log.debug('Notifications: Sent push token (event)', {
5454- type,
5454+ tokenType: type,
5555 token: t,
5656 })
5757 } catch (error) {
5858- store.log.error('Notifications: Failed to set push token', error)
5858+ store.log.error('Notifications: Failed to set push token', {error})
5959 }
6060 }
6161 })
···63636464 // handle notifications that are received, both in the foreground or background
6565 Notifications.addNotificationReceivedListener(event => {
6666- store.log.debug('Notifications: received', event)
6666+ store.log.debug('Notifications: received', {event})
6767 if (event.request.trigger.type === 'push') {
6868 // refresh notifications in the background
6969 store.me.notifications.syncQueue()
···8484 // handle notifications that are tapped on
8585 const sub = Notifications.addNotificationResponseReceivedListener(
8686 response => {
8787- store.log.debug(
8888- 'Notifications: response received',
8989- response.actionIdentifier,
9090- )
8787+ store.log.debug('Notifications: response received', {
8888+ actionIdentifier: response.actionIdentifier,
8989+ })
9190 if (
9291 response.actionIdentifier === Notifications.DEFAULT_ACTION_IDENTIFIER
9392 ) {
+99
src/logger/README.md
···11+# Logger
22+33+Simple logger for Bluesky. Supports log levels, debug contexts, and separate
44+transports for production, dev, and test mode.
55+66+## At a Glance
77+88+```typescript
99+import { logger } from '#/logger'
1010+1111+logger.debug(message[, metadata, debugContext])
1212+logger.info(message[, metadata])
1313+logger.log(message[, metadata])
1414+logger.warn(message[, metadata])
1515+logger.error(error[, metadata])
1616+```
1717+1818+#### Modes
1919+2020+The "modes" referred to here are inferred from the values exported from `#/env`.
2121+Basically, the booleans `IS_DEV`, `IS_TEST`, and `IS_PROD`.
2222+2323+#### Log Levels
2424+2525+Log levels are used to filter which logs are either printed to the console
2626+and/or sent to Sentry and other reporting services. To configure, set the
2727+`EXPO_PUBLIC_LOG_LEVEL` environment variable in `.env` to one of `debug`,
2828+`info`, `log`, `warn`, or `error`.
2929+3030+This variable should be `info` in production, and `debug` in dev. If it gets too
3131+noisy in dev, simply set it to a higher level, such as `warn`.
3232+3333+## Usage
3434+3535+```typescript
3636+import { logger } from '#/logger';
3737+```
3838+3939+### `logger.error`
4040+4141+The `error` level is for... well, errors. These are sent to Sentry in production mode.
4242+4343+`error`, along with all log levels, supports an additional parameter, `metadata: Record<string, unknown>`. Use this to provide values to the [Sentry
4444+breadcrumb](https://docs.sentry.io/platforms/react-native/enriching-events/breadcrumbs/#manual-breadcrumbs).
4545+4646+```typescript
4747+try {
4848+ // some async code
4949+} catch (e) {
5050+ logger.error(e, { ...metadata });
5151+}
5252+```
5353+5454+### `logger.warn`
5555+5656+Warnings will be sent to Sentry as a separate Issue with level `warning`, as
5757+well as as breadcrumbs, with a severity level of `warning`
5858+5959+### `logger.log`
6060+6161+Logs with level `log` will be sent to Sentry as a separate Issue with level `log`, as
6262+well as as breadcrumbs, with a severity level of `default`.
6363+6464+### `logger.info`
6565+6666+The `info` level should be used for information that would be helpful in a
6767+tracing context, like Sentry. In production mode, `info` logs are sent
6868+to Sentry as breadcrumbs, which decorate log levels above `info` such as `log`,
6969+`warn`, and `error`.
7070+7171+### `logger.debug`
7272+7373+Debug level is really only intended for local development. Use this instead of
7474+`console.log`.
7575+7676+```typescript
7777+logger.debug(message, { ...metadata });
7878+```
7979+8080+Inspired by [debug](https://www.npmjs.com/package/debug), when writing debug
8181+logs, you can optionally pass a _context_, which can be then filtered when in
8282+debug mode.
8383+8484+This value should be related to the feature, component, or screen
8585+the code is running within, and **it should be defined in `#/logger/debugContext`**.
8686+This way we know if a relevant context already exists, and we can trace all
8787+active contexts in use in our app. This const enum is conveniently available on
8888+the `logger` at `logger.DebugContext`.
8989+9090+For example, a debug log like this:
9191+9292+```typescript
9393+logger.debug(message, {}, logger.DebugContext.composer);
9494+```
9595+9696+Would be logged to the console in dev mode if `EXPO_PUBLIC_LOG_LEVEL=debug`, _or_ if you
9797+pass a separate environment variable `LOG_DEBUG=composer`. This variable supports
9898+multiple contexts using commas like `LOG_DEBUG=composer,profile`, and _automatically
9999+sets the log level to `debug`, regardless of `EXPO_PUBLIC_LOG_LEVEL`._
···11+/**
22+ * *Do not import this directly.* Instead, use the shortcut reference `logger.DebugContext`.
33+ *
44+ * Add debug contexts here. Although convention typically calls for enums ito
55+ * be capitalized, for parity with the `LOG_DEBUG` env var, please use all
66+ * lowercase.
77+ */
88+export const DebugContext = {
99+ // e.g. composer: 'composer'
1010+} as const
+290
src/logger/index.ts
···11+import format from 'date-fns/format'
22+import {nanoid} from 'nanoid/non-secure'
33+44+import {Sentry} from '#/logger/sentry'
55+import * as env from '#/env'
66+import {DebugContext} from '#/logger/debugContext'
77+import {add} from '#/logger/logDump'
88+99+export enum LogLevel {
1010+ Debug = 'debug',
1111+ Info = 'info',
1212+ Log = 'log',
1313+ Warn = 'warn',
1414+ Error = 'error',
1515+}
1616+1717+type Transport = (
1818+ level: LogLevel,
1919+ message: string | Error,
2020+ metadata: Metadata,
2121+ timestamp: number,
2222+) => void
2323+2424+/**
2525+ * A union of some of Sentry's breadcrumb properties as well as Sentry's
2626+ * `captureException` parameter, `CaptureContext`.
2727+ */
2828+type Metadata = {
2929+ /**
3030+ * Applied as Sentry breadcrumb types. Defaults to `default`.
3131+ *
3232+ * @see https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types
3333+ */
3434+ type?:
3535+ | 'default'
3636+ | 'debug'
3737+ | 'error'
3838+ | 'navigation'
3939+ | 'http'
4040+ | 'info'
4141+ | 'query'
4242+ | 'transaction'
4343+ | 'ui'
4444+ | 'user'
4545+4646+ /**
4747+ * Passed through to `Sentry.captureException`
4848+ *
4949+ * @see https://github.com/getsentry/sentry-javascript/blob/903addf9a1a1534a6cb2ba3143654b918a86f6dd/packages/types/src/misc.ts#L65
5050+ */
5151+ tags?: {
5252+ [key: string]:
5353+ | number
5454+ | string
5555+ | boolean
5656+ | bigint
5757+ | symbol
5858+ | null
5959+ | undefined
6060+ }
6161+6262+ /**
6363+ * Any additional data, passed through to Sentry as `extra` param on
6464+ * exceptions, or the `data` param on breadcrumbs.
6565+ */
6666+ [key: string]: unknown
6767+} & Parameters<typeof Sentry.captureException>[1]
6868+6969+export type ConsoleTransportEntry = {
7070+ id: string
7171+ timestamp: number
7272+ level: LogLevel
7373+ message: string | Error
7474+ metadata: Metadata
7575+}
7676+7777+const enabledLogLevels: {
7878+ [key in LogLevel]: LogLevel[]
7979+} = {
8080+ [LogLevel.Debug]: [
8181+ LogLevel.Debug,
8282+ LogLevel.Info,
8383+ LogLevel.Log,
8484+ LogLevel.Warn,
8585+ LogLevel.Error,
8686+ ],
8787+ [LogLevel.Info]: [LogLevel.Info, LogLevel.Log, LogLevel.Warn, LogLevel.Error],
8888+ [LogLevel.Log]: [LogLevel.Log, LogLevel.Warn, LogLevel.Error],
8989+ [LogLevel.Warn]: [LogLevel.Warn, LogLevel.Error],
9090+ [LogLevel.Error]: [LogLevel.Error],
9191+}
9292+9393+/**
9494+ * Used in dev mode to nicely log to the console
9595+ */
9696+export const consoleTransport: Transport = (
9797+ level,
9898+ message,
9999+ metadata,
100100+ timestamp,
101101+) => {
102102+ const extra = Object.keys(metadata).length
103103+ ? ' ' + JSON.stringify(metadata, null, ' ')
104104+ : ''
105105+ const log = {
106106+ [LogLevel.Debug]: console.debug,
107107+ [LogLevel.Info]: console.info,
108108+ [LogLevel.Log]: console.log,
109109+ [LogLevel.Warn]: console.warn,
110110+ [LogLevel.Error]: console.error,
111111+ }[level]
112112+113113+ log(`${format(timestamp, 'HH:mm:ss')} ${message.toString()}${extra}`)
114114+}
115115+116116+export const sentryTransport: Transport = (
117117+ level,
118118+ message,
119119+ {type, tags, ...metadata},
120120+ timestamp,
121121+) => {
122122+ /**
123123+ * If a string, report a breadcrumb
124124+ */
125125+ if (typeof message === 'string') {
126126+ const severity = (
127127+ {
128128+ [LogLevel.Debug]: 'debug',
129129+ [LogLevel.Info]: 'info',
130130+ [LogLevel.Log]: 'log', // Sentry value here is undefined
131131+ [LogLevel.Warn]: 'warning',
132132+ [LogLevel.Error]: 'error',
133133+ } as const
134134+ )[level]
135135+136136+ Sentry.addBreadcrumb({
137137+ message,
138138+ data: metadata,
139139+ type: type || 'default',
140140+ level: severity,
141141+ timestamp: timestamp / 1000, // Sentry expects seconds
142142+ })
143143+144144+ /**
145145+ * Send all higher levels with `captureMessage`, with appropriate severity
146146+ * level
147147+ */
148148+ if (level === 'error' || level === 'warn' || level === 'log') {
149149+ const messageLevel = ({
150150+ [LogLevel.Log]: 'log',
151151+ [LogLevel.Warn]: 'warning',
152152+ [LogLevel.Error]: 'error',
153153+ }[level] || 'log') as Sentry.Breadcrumb['level']
154154+155155+ Sentry.captureMessage(message, {
156156+ level: messageLevel,
157157+ tags,
158158+ extra: metadata,
159159+ })
160160+ }
161161+ } else {
162162+ /**
163163+ * It's otherwise an Error and should be reported with captureException
164164+ */
165165+ Sentry.captureException(message, {
166166+ tags,
167167+ extra: metadata,
168168+ })
169169+ }
170170+}
171171+172172+/**
173173+ * Main class. Defaults are provided in the constructor so that subclasses are
174174+ * technically possible, if we need to go that route in the future.
175175+ */
176176+export class Logger {
177177+ LogLevel = LogLevel
178178+ DebugContext = DebugContext
179179+180180+ enabled: boolean
181181+ level: LogLevel
182182+ transports: Transport[] = []
183183+184184+ protected debugContextRegexes: RegExp[] = []
185185+186186+ constructor({
187187+ enabled = !env.IS_TEST,
188188+ level = env.LOG_LEVEL as LogLevel,
189189+ debug = env.LOG_DEBUG || '',
190190+ }: {
191191+ enabled?: boolean
192192+ level?: LogLevel
193193+ debug?: string
194194+ } = {}) {
195195+ this.enabled = enabled !== false
196196+ this.level = debug ? LogLevel.Debug : level ?? LogLevel.Info // default to info
197197+ this.debugContextRegexes = (debug || '').split(',').map(context => {
198198+ return new RegExp(context.replace(/[^\w:*]/, '').replace(/\*/g, '.*'))
199199+ })
200200+ }
201201+202202+ debug(message: string, metadata: Metadata = {}, context?: string) {
203203+ if (context && !this.debugContextRegexes.find(reg => reg.test(context)))
204204+ return
205205+ this.transport(LogLevel.Debug, message, metadata)
206206+ }
207207+208208+ info(message: string, metadata: Metadata = {}) {
209209+ this.transport(LogLevel.Info, message, metadata)
210210+ }
211211+212212+ log(message: string, metadata: Metadata = {}) {
213213+ this.transport(LogLevel.Log, message, metadata)
214214+ }
215215+216216+ warn(message: string, metadata: Metadata = {}) {
217217+ this.transport(LogLevel.Warn, message, metadata)
218218+ }
219219+220220+ error(error: Error | string, metadata: Metadata = {}) {
221221+ this.transport(LogLevel.Error, error, metadata)
222222+ }
223223+224224+ addTransport(transport: Transport) {
225225+ this.transports.push(transport)
226226+ return () => {
227227+ this.transports.splice(this.transports.indexOf(transport), 1)
228228+ }
229229+ }
230230+231231+ disable() {
232232+ this.enabled = false
233233+ }
234234+235235+ enable() {
236236+ this.enabled = true
237237+ }
238238+239239+ protected transport(
240240+ level: LogLevel,
241241+ message: string | Error,
242242+ metadata: Metadata = {},
243243+ ) {
244244+ if (!this.enabled) return
245245+ if (!enabledLogLevels[this.level].includes(level)) return
246246+247247+ const timestamp = Date.now()
248248+ const meta = metadata || {}
249249+250250+ for (const transport of this.transports) {
251251+ transport(level, message, meta, timestamp)
252252+ }
253253+254254+ add({
255255+ id: nanoid(),
256256+ timestamp,
257257+ level,
258258+ message,
259259+ metadata: meta,
260260+ })
261261+ }
262262+}
263263+264264+/**
265265+ * Logger instance. See `@/logger/README` for docs.
266266+ *
267267+ * Basic usage:
268268+ *
269269+ * `logger.debug(message[, metadata, debugContext])`
270270+ * `logger.info(message[, metadata])`
271271+ * `logger.warn(message[, metadata])`
272272+ * `logger.error(error[, metadata])`
273273+ * `logger.disable()`
274274+ * `logger.enable()`
275275+ */
276276+export const logger = new Logger()
277277+278278+/**
279279+ * Report to console in dev, Sentry in prod, nothing in test.
280280+ */
281281+if (env.IS_DEV && !env.IS_TEST) {
282282+ logger.addTransport(consoleTransport)
283283+284284+ /**
285285+ * Uncomment this to test Sentry in dev
286286+ */
287287+ // logger.addTransport(sentryTransport);
288288+} else if (env.IS_PROD) {
289289+ // logger.addTransport(sentryTransport)
290290+}
+12
src/logger/logDump.ts
···11+import type {ConsoleTransportEntry} from '#/logger'
22+33+let entries: ConsoleTransportEntry[] = []
44+55+export function add(entry: ConsoleTransportEntry) {
66+ entries.unshift(entry)
77+ entries = entries.slice(0, 50)
88+}
99+1010+export function getEntries() {
1111+ return entries
1212+}
+1
src/logger/sentry/index.ts
···11+export {Native as Sentry} from 'sentry-expo'
+1
src/logger/sentry/index.web.ts
···11+export {Browser as Sentry} from 'sentry-expo'
+1-1
src/state/index.ts
···2525 rootStore.log.debug('Initial hydrate', {hasSession: !!data.session})
2626 rootStore.hydrate(data)
2727 } catch (e: any) {
2828- rootStore.log.error('Failed to load state from storage', e)
2828+ rootStore.log.error('Failed to load state from storage', {error: e})
2929 }
3030 rootStore.attemptSessionResumption()
3131
···88import {DeviceEventEmitter, EmitterSubscription} from 'react-native'
99import {z} from 'zod'
1010import {isObj, hasProp} from 'lib/type-guards'
1111-import {LogModel} from './log'
1211import {SessionModel} from './session'
1312import {ShellUiModel} from './ui/shell'
1413import {HandleResolutionsCache} from './cache/handle-resolutions'
···2322import {MutedThreads} from './muted-threads'
2423import {Reminders} from './ui/reminders'
2524import {reset as resetNavigation} from '../../Navigation'
2525+import {logger} from '#/logger'
26262727// TEMPORARY (APP-700)
2828// remove after backend testing finishes
···4141export class RootStoreModel {
4242 agent: BskyAgent
4343 appInfo?: AppInfo
4444- log = new LogModel()
4444+ log = logger
4545 session = new SessionModel(this)
4646 shell = new ShellUiModel(this)
4747 preferences = new PreferencesModel(this)
···130130 })
131131 this.updateSessionState()
132132 } catch (e: any) {
133133- this.log.warn('Failed to initialize session', e)
133133+ this.log.warn('Failed to initialize session', {error: e})
134134 }
135135 }
136136···184184 await this.me.updateIfNeeded()
185185 await this.preferences.sync()
186186 } catch (e: any) {
187187- this.log.error('Failed to fetch latest state', e)
187187+ this.log.error('Failed to fetch latest state', {error: e})
188188 }
189189 }
190190
+2-2
src/state/models/ui/create-account.ts
···7878 } catch (err: any) {
7979 this.rootStore.log.warn(
8080 `Failed to fetch service description for ${this.serviceUrl}`,
8181- err,
8181+ {error: err},
8282 )
8383 this.setError(
8484 'Unable to contact your service. Please check your Internet connection.',
···127127 errMsg =
128128 'Invite code not accepted. Check that you input it correctly and try again.'
129129 }
130130- this.rootStore.log.error('Failed to create account', e)
130130+ this.rootStore.log.error('Failed to create account', {error: e})
131131 this.setIsProcessing(false)
132132 this.setError(cleanError(errMsg))
133133 throw e
···4646 setExtLink(undefined)
4747 },
4848 err => {
4949- store.log.error('Failed to fetch post for quote embedding', {err})
4949+ store.log.error('Failed to fetch post for quote embedding', {
5050+ error: err,
5151+ })
5052 setExtLink(undefined)
5153 },
5254 )
···6466 })
6567 },
6668 err => {
6767- store.log.error('Failed to fetch feed for embedding', {err})
6969+ store.log.error('Failed to fetch feed for embedding', {error: err})
6870 setExtLink(undefined)
6971 },
7072 )
···8284 })
8385 },
8486 err => {
8585- store.log.error('Failed to fetch list for embedding', {err})
8787+ store.log.error('Failed to fetch list for embedding', {error: err})
8688 setExtLink(undefined)
8789 },
8890 )
+2-2
src/view/com/feeds/FeedSourceCard.tsx
···4545 Toast.show('Removed from my feeds')
4646 } catch (e) {
4747 Toast.show('There was an issue contacting your server')
4848- store.log.error('Failed to unsave feed', {e})
4848+ store.log.error('Failed to unsave feed', {error: e})
4949 }
5050 },
5151 })
···5555 Toast.show('Added to my feeds')
5656 } catch (e) {
5757 Toast.show('There was an issue contacting your server')
5858- store.log.error('Failed to save feed', {e})
5858+ store.log.error('Failed to save feed', {error: e})
5959 }
6060 }
6161 }, [store, item])
···6969 `Failed to fetch service description for ${String(
7070 store.agent.service,
7171 )}`,
7272- err,
7272+ {error: err},
7373 )
7474 setError(
7575 'Unable to contact your service. Please check your Internet connection.',
···113113 onChanged()
114114 } catch (err: any) {
115115 setError(cleanError(err))
116116- store.log.error('Failed to update handle', {handle, err})
116116+ store.log.error('Failed to update handle', {handle, error: err})
117117 } finally {
118118 setProcessing(false)
119119 }
···343343 }
344344 } catch (err: any) {
345345 setError(cleanError(err))
346346- store.log.error('Failed to verify domain', {handle, err})
346346+ store.log.error('Failed to verify domain', {handle, error: err})
347347 } finally {
348348 setIsVerifying(false)
349349 }
+2-2
src/view/com/modals/ContentFilteringSettings.tsx
···103103 Toast.show(
104104 'There was an issue syncing your preferences with the server',
105105 )
106106- store.log.error('Failed to update preferences with server', {e})
106106+ store.log.error('Failed to update preferences with server', {error: e})
107107 }
108108 }
109109···168168 Toast.show(
169169 'There was an issue syncing your preferences with the server',
170170 )
171171- store.log.error('Failed to update preferences with server', {e})
171171+ store.log.error('Failed to update preferences with server', {error: e})
172172 }
173173 },
174174 [store, group],
···7373 Toast.show(
7474 'There was an an issue removing this feed. Please check your internet connection and try again.',
7575 )
7676- store.log.error('Failed to remove feed', {err})
7676+ store.log.error('Failed to remove feed', {error: err})
7777 }
7878 },
7979 onPressCancel() {
+4-4
src/view/com/posts/FeedItem.tsx
···9494 track('FeedItem:PostRepost')
9595 return item
9696 .toggleRepost()
9797- .catch(e => store.log.error('Failed to toggle repost', e))
9797+ .catch(e => store.log.error('Failed to toggle repost', {error: e}))
9898 }, [track, item, store])
9999100100 const onPressToggleLike = React.useCallback(() => {
101101 track('FeedItem:PostLike')
102102 return item
103103 .toggleLike()
104104- .catch(e => store.log.error('Failed to toggle like', e))
104104+ .catch(e => store.log.error('Failed to toggle like', {error: e}))
105105 }, [track, item, store])
106106107107 const onCopyPostText = React.useCallback(() => {
···123123 Toast.show('You will now receive notifications for this thread')
124124 }
125125 } catch (e) {
126126- store.log.error('Failed to toggle thread mute', e)
126126+ store.log.error('Failed to toggle thread mute', {error: e})
127127 }
128128 }, [track, item, store])
129129···135135 Toast.show('Post deleted')
136136 },
137137 e => {
138138- store.log.error('Failed to delete post', e)
138138+ store.log.error('Failed to delete post', {error: e})
139139 Toast.show('Failed to delete post, please try again')
140140 },
141141 )
···108108 uiState
109109 .refresh()
110110 .catch((err: any) =>
111111- store.log.error('Failed to refresh user profile', err),
111111+ store.log.error('Failed to refresh user profile', {error: err}),
112112 )
113113 }, [uiState, store])
114114 const onEndReached = React.useCallback(() => {
115115- uiState
116116- .loadMore()
117117- .catch((err: any) =>
118118- store.log.error('Failed to load more entries in user profile', err),
119119- )
115115+ uiState.loadMore().catch((err: any) =>
116116+ store.log.error('Failed to load more entries in user profile', {
117117+ error: err,
118118+ }),
119119+ )
120120 }, [uiState, store])
121121 const onPressTryAgain = React.useCallback(() => {
122122 uiState.setup()
+3-3
src/view/screens/ProfileFeed.tsx
···165165 Toast.show(
166166 'There was an an issue updating your feeds, please check your internet connection and try again.',
167167 )
168168- store.log.error('Failed up update feeds', {err})
168168+ store.log.error('Failed up update feeds', {error: err})
169169 }
170170 }, [store, feedInfo])
171171···181181 Toast.show(
182182 'There was an an issue contacting the server, please check your internet connection and try again.',
183183 )
184184- store.log.error('Failed up toggle like', {err})
184184+ store.log.error('Failed up toggle like', {error: err})
185185 }
186186 }, [store, feedInfo])
187187···190190 if (feedInfo) {
191191 feedInfo.togglePin().catch(e => {
192192 Toast.show('There was an issue contacting the server')
193193- store.log.error('Failed to toggle pinned feed', {e})
193193+ store.log.error('Failed to toggle pinned feed', {error: e})
194194 })
195195 }
196196 }, [store, feedInfo])
+1-1
src/view/screens/ProfileList.tsx
···272272 Haptics.default()
273273 list.togglePin().catch(e => {
274274 Toast.show('There was an issue contacting the server')
275275- store.log.error('Failed to toggle pinned list', {e})
275275+ store.log.error('Failed to toggle pinned list', {error: e})
276276 })
277277 }, [store, list])
278278
+3-3
src/view/screens/SavedFeeds.tsx
···166166 Haptics.default()
167167 item.togglePin().catch(e => {
168168 Toast.show('There was an issue contacting the server')
169169- store.log.error('Failed to toggle pinned feed', {e})
169169+ store.log.error('Failed to toggle pinned feed', {error: e})
170170 })
171171 }, [item, store])
172172 const onPressUp = useCallback(
173173 () =>
174174 savedFeeds.movePinnedFeed(item, 'up').catch(e => {
175175 Toast.show('There was an issue contacting the server')
176176- store.log.error('Failed to set pinned feed order', {e})
176176+ store.log.error('Failed to set pinned feed order', {error: e})
177177 }),
178178 [store, savedFeeds, item],
179179 )
···181181 () =>
182182 savedFeeds.movePinnedFeed(item, 'down').catch(e => {
183183 Toast.show('There was an issue contacting the server')
184184- store.log.error('Failed to set pinned feed order', {e})
184184+ store.log.error('Failed to set pinned feed order', {error: e})
185185 }),
186186 [store, savedFeeds, item],
187187 )
+1-1
src/view/screens/Settings.tsx
···112112 err => {
113113 store.log.error(
114114 'Failed to reload from server after handle update',
115115- {err},
115115+ {error: err},
116116 )
117117 setIsSwitching(false)
118118 },