mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-28 22:27:14 +02:00
Add AbsLogger plugin, persist logs to db, logs page for android
This commit is contained in:
@@ -18,6 +18,7 @@ import com.audiobookshelf.app.plugins.AbsAudioPlayer
|
|||||||
import com.audiobookshelf.app.plugins.AbsDatabase
|
import com.audiobookshelf.app.plugins.AbsDatabase
|
||||||
import com.audiobookshelf.app.plugins.AbsDownloader
|
import com.audiobookshelf.app.plugins.AbsDownloader
|
||||||
import com.audiobookshelf.app.plugins.AbsFileSystem
|
import com.audiobookshelf.app.plugins.AbsFileSystem
|
||||||
|
import com.audiobookshelf.app.plugins.AbsLogger
|
||||||
import com.getcapacitor.BridgeActivity
|
import com.getcapacitor.BridgeActivity
|
||||||
|
|
||||||
|
|
||||||
@@ -57,6 +58,7 @@ class MainActivity : BridgeActivity() {
|
|||||||
registerPlugin(AbsDownloader::class.java)
|
registerPlugin(AbsDownloader::class.java)
|
||||||
registerPlugin(AbsFileSystem::class.java)
|
registerPlugin(AbsFileSystem::class.java)
|
||||||
registerPlugin(AbsDatabase::class.java)
|
registerPlugin(AbsDatabase::class.java)
|
||||||
|
registerPlugin(AbsLogger::class.java)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(tag, "onCreate")
|
Log.d(tag, "onCreate")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.models.DownloadItem
|
import com.audiobookshelf.app.models.DownloadItem
|
||||||
|
import com.audiobookshelf.app.plugins.AbsLog
|
||||||
import io.paperdb.Paper
|
import io.paperdb.Paper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@@ -287,4 +288,17 @@ class DbManager {
|
|||||||
}
|
}
|
||||||
return sessions
|
return sessions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveLog(log:AbsLog) {
|
||||||
|
Paper.book("log").write(log.id, log)
|
||||||
|
}
|
||||||
|
fun getAllLogs() : List<AbsLog> {
|
||||||
|
val logs:MutableList<AbsLog> = mutableListOf()
|
||||||
|
Paper.book("log").allKeys.forEach { logId ->
|
||||||
|
Paper.book("log").read<AbsLog>(logId)?.let {
|
||||||
|
logs.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return logs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -219,6 +219,7 @@ class AbsDatabase : Plugin() {
|
|||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun syncLocalSessionsWithServer(call:PluginCall) {
|
fun syncLocalSessionsWithServer(call:PluginCall) {
|
||||||
|
AbsLogger.info("[AbsDatabase] syncLocalSessionsWithServer")
|
||||||
if (DeviceManager.serverConnectionConfig == null) {
|
if (DeviceManager.serverConnectionConfig == null) {
|
||||||
Log.e(tag, "syncLocalSessionsWithServer not connected to server")
|
Log.e(tag, "syncLocalSessionsWithServer not connected to server")
|
||||||
return call.resolve()
|
return call.resolve()
|
||||||
@@ -226,6 +227,7 @@ class AbsDatabase : Plugin() {
|
|||||||
|
|
||||||
apiHandler.syncLocalMediaProgressForUser {
|
apiHandler.syncLocalMediaProgressForUser {
|
||||||
Log.d(tag, "Finished syncing local media progress for user")
|
Log.d(tag, "Finished syncing local media progress for user")
|
||||||
|
AbsLogger.info("[AbsDatabase] Finished syncing local media progress for user")
|
||||||
val savedSessions = DeviceManager.dbManager.getPlaybackSessions().filter { it.serverConnectionConfigId == DeviceManager.serverConnectionConfigId }
|
val savedSessions = DeviceManager.dbManager.getPlaybackSessions().filter { it.serverConnectionConfigId == DeviceManager.serverConnectionConfigId }
|
||||||
|
|
||||||
if (savedSessions.isNotEmpty()) {
|
if (savedSessions.isNotEmpty()) {
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.audiobookshelf.app.plugins
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import com.getcapacitor.JSObject
|
||||||
|
import com.getcapacitor.Plugin
|
||||||
|
import com.getcapacitor.PluginCall
|
||||||
|
import com.getcapacitor.PluginMethod
|
||||||
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
data class AbsLog(
|
||||||
|
var id:String,
|
||||||
|
var level:String,
|
||||||
|
var message:String,
|
||||||
|
var timestamp:Long
|
||||||
|
)
|
||||||
|
|
||||||
|
data class AbsLogList(val value:List<AbsLog>)
|
||||||
|
|
||||||
|
@CapacitorPlugin(name = "AbsLogger")
|
||||||
|
class AbsLogger : Plugin() {
|
||||||
|
private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
|
override fun load() {
|
||||||
|
Log.i("AbsLogger", "Initialize AbsLogger plugin")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun info(message:String) {
|
||||||
|
Log.i("AbsLogger", message)
|
||||||
|
DeviceManager.dbManager.saveLog(AbsLog(id = UUID.randomUUID().toString(), level = "info", message, timestamp = System.currentTimeMillis()))
|
||||||
|
}
|
||||||
|
fun error(message:String) {
|
||||||
|
Log.e("AbsLogger", message)
|
||||||
|
DeviceManager.dbManager.saveLog(AbsLog(id = UUID.randomUUID().toString(), level = "error", message, timestamp = System.currentTimeMillis()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun info(call: PluginCall) {
|
||||||
|
val msg = call.getString("message") ?: return call.reject("No message")
|
||||||
|
info(msg)
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun error(call: PluginCall) {
|
||||||
|
val msg = call.getString("message") ?: return call.reject("No message")
|
||||||
|
error(msg)
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun getAllLogs(call: PluginCall) {
|
||||||
|
val absLogs = DeviceManager.dbManager.getAllLogs()
|
||||||
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(AbsLogList(absLogs))))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -134,6 +134,15 @@ export default {
|
|||||||
to: '/settings'
|
to: '/settings'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (this.$platform !== 'ios') {
|
||||||
|
items.push({
|
||||||
|
icon: 'bug_report',
|
||||||
|
iconOutlined: true,
|
||||||
|
text: this.$strings.ButtonLogs,
|
||||||
|
to: '/logs'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (this.serverConnectionConfig) {
|
if (this.serverConnectionConfig) {
|
||||||
items.push({
|
items.push({
|
||||||
icon: 'language',
|
icon: 'language',
|
||||||
|
|||||||
+15
-5
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { CapacitorHttp } from '@capacitor/core'
|
import { CapacitorHttp } from '@capacitor/core'
|
||||||
|
import { AbsLogger } from '@/plugins/capacitor'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
@@ -72,6 +73,9 @@ export default {
|
|||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
|
currentLibraryName() {
|
||||||
|
return this.$store.getters['libraries/getCurrentLibraryName']
|
||||||
|
},
|
||||||
attemptingConnection: {
|
attemptingConnection: {
|
||||||
get() {
|
get() {
|
||||||
return this.$store.state.attemptingConnection
|
return this.$store.state.attemptingConnection
|
||||||
@@ -114,6 +118,7 @@ export default {
|
|||||||
console.warn('[default] attemptConnection')
|
console.warn('[default] attemptConnection')
|
||||||
if (!this.networkConnected) {
|
if (!this.networkConnected) {
|
||||||
console.warn('[default] No network connection')
|
console.warn('[default] No network connection')
|
||||||
|
AbsLogger.info({ message: '[default] attemptConnection: No network connection' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.attemptingConnection) {
|
if (this.attemptingConnection) {
|
||||||
@@ -134,13 +139,14 @@ export default {
|
|||||||
if (!serverConfig) {
|
if (!serverConfig) {
|
||||||
// No last server config set
|
// No last server config set
|
||||||
this.attemptingConnection = false
|
this.attemptingConnection = false
|
||||||
|
AbsLogger.info({ message: `[default] attemptConnection: No last server config set` })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[default] Got server config, attempt authorize ${serverConfig.address}`)
|
AbsLogger.info({ message: `[default] attemptConnection: Got server config, attempt authorize (${serverConfig.name})` })
|
||||||
|
|
||||||
const authRes = await this.postRequest(`${serverConfig.address}/api/authorize`, null, { Authorization: `Bearer ${serverConfig.token}` }, 6000).catch((error) => {
|
const authRes = await this.postRequest(`${serverConfig.address}/api/authorize`, null, { Authorization: `Bearer ${serverConfig.token}` }, 6000).catch((error) => {
|
||||||
console.error('[default] Server auth failed', error)
|
AbsLogger.error({ message: `[default] attemptConnection: Server auth failed (${serverConfig.name})` })
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
if (!authRes) {
|
if (!authRes) {
|
||||||
@@ -166,7 +172,7 @@ export default {
|
|||||||
|
|
||||||
this.$socket.connect(serverConnectionConfig.address, serverConnectionConfig.token)
|
this.$socket.connect(serverConnectionConfig.address, serverConnectionConfig.token)
|
||||||
|
|
||||||
console.log('[default] Successful connection on last saved connection config', JSON.stringify(serverConnectionConfig))
|
AbsLogger.info({ message: `[default] attemptConnection: Successful connection to last saved server config (${serverConnectionConfig.name})` })
|
||||||
await this.initLibraries()
|
await this.initLibraries()
|
||||||
this.attemptingConnection = false
|
this.attemptingConnection = false
|
||||||
},
|
},
|
||||||
@@ -186,7 +192,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.inittingLibraries = true
|
this.inittingLibraries = true
|
||||||
await this.$store.dispatch('libraries/load')
|
await this.$store.dispatch('libraries/load')
|
||||||
console.log(`[default] initLibraries loaded ${this.currentLibraryId}`)
|
|
||||||
|
AbsLogger.info({ message: `[default] initLibraries loading library ${this.currentLibraryName}` })
|
||||||
await this.$store.dispatch('libraries/fetch', this.currentLibraryId)
|
await this.$store.dispatch('libraries/fetch', this.currentLibraryId)
|
||||||
this.$eventBus.$emit('library-changed')
|
this.$eventBus.$emit('library-changed')
|
||||||
this.inittingLibraries = false
|
this.inittingLibraries = false
|
||||||
@@ -197,7 +204,7 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[default] Calling syncLocalSessions')
|
AbsLogger.info({ message: '[default] Calling syncLocalSessions' })
|
||||||
const response = await this.$db.syncLocalSessionsWithServer(isFirstSync)
|
const response = await this.$db.syncLocalSessionsWithServer(isFirstSync)
|
||||||
if (response?.error) {
|
if (response?.error) {
|
||||||
console.error('[default] Failed to sync local sessions', response.error)
|
console.error('[default] Failed to sync local sessions', response.error)
|
||||||
@@ -310,6 +317,7 @@ export default {
|
|||||||
this.$socket.on('user_media_progress_updated', this.userMediaProgressUpdated)
|
this.$socket.on('user_media_progress_updated', this.userMediaProgressUpdated)
|
||||||
|
|
||||||
if (this.$store.state.isFirstLoad) {
|
if (this.$store.state.isFirstLoad) {
|
||||||
|
AbsLogger.info({ message: `[default] mounted: first load` })
|
||||||
this.$store.commit('setIsFirstLoad', false)
|
this.$store.commit('setIsFirstLoad', false)
|
||||||
|
|
||||||
this.loadSavedSettings()
|
this.loadSavedSettings()
|
||||||
@@ -322,8 +330,10 @@ export default {
|
|||||||
await this.$store.dispatch('setupNetworkListener')
|
await this.$store.dispatch('setupNetworkListener')
|
||||||
|
|
||||||
if (this.$store.state.user.serverConnectionConfig) {
|
if (this.$store.state.user.serverConnectionConfig) {
|
||||||
|
AbsLogger.info({ message: `[default] Server connected, init libraries (ServerConfigName: ${this.$store.getters['user/getServerConfigName']})` })
|
||||||
await this.initLibraries()
|
await this.initLibraries()
|
||||||
} else {
|
} else {
|
||||||
|
AbsLogger.info({ message: `[default] Server not connected, attempt connection` })
|
||||||
await this.attemptConnection()
|
await this.attemptConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<template>
|
||||||
|
<div class="w-full h-full p-4">
|
||||||
|
<div class="flex items-center justify-between mb-2">
|
||||||
|
<p class="text-lg font-bold">{{ $strings.ButtonLogs }}</p>
|
||||||
|
<ui-icon-btn outlined borderless icon="content_copy" @click="copyToClipboard" />
|
||||||
|
</div>
|
||||||
|
<div class="w-full h-[calc(100%-40px)] overflow-y-auto relative" ref="logContainer">
|
||||||
|
<div v-if="hasScrolled" class="sticky top-0 left-0 w-full h-10 bg-gradient-to-t from-transparent to-bg z-10 pointer-events-none"></div>
|
||||||
|
|
||||||
|
<div v-for="log in logs" :key="log.id" class="py-1">
|
||||||
|
<div class="flex items-center space-x-4 mb-1">
|
||||||
|
<div class="text-xs uppercase font-bold" :class="{ 'text-error': log.level === 'error', 'text-blue-600': log.level === 'info' }">{{ log.level }}</div>
|
||||||
|
<div class="text-xs text-gray-400">{{ new Date(log.timestamp).toLocaleString() }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs">{{ log.message }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { AbsLogger } from '@/plugins/capacitor'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
logs: [],
|
||||||
|
hasScrolled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {},
|
||||||
|
methods: {
|
||||||
|
copyToClipboard() {
|
||||||
|
this.$copyToClipboard(
|
||||||
|
this.logs
|
||||||
|
.map((log) => {
|
||||||
|
return `${log.timestamp} [${log.level}] ${log.message}`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scrollToBottom() {
|
||||||
|
this.$refs.logContainer.scrollTop = this.$refs.logContainer.scrollHeight
|
||||||
|
this.hasScrolled = this.$refs.logContainer.scrollTop > 0
|
||||||
|
},
|
||||||
|
loadLogs() {
|
||||||
|
AbsLogger.getAllLogs().then((logData) => {
|
||||||
|
const logs = logData.value || []
|
||||||
|
this.logs = logs
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.scrollToBottom()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.loadLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { registerPlugin, WebPlugin } from '@capacitor/core'
|
||||||
|
|
||||||
|
class AbsLoggerWeb extends WebPlugin {
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.logs = []
|
||||||
|
}
|
||||||
|
|
||||||
|
saveLog(level, message) {
|
||||||
|
this.logs.push({
|
||||||
|
id: Math.random().toString(36).substring(2, 15),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
level: level,
|
||||||
|
message: message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async info(data) {
|
||||||
|
if (data?.message) {
|
||||||
|
this.saveLog('info', data.message)
|
||||||
|
console.log('AbsLogger: info', data.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async error(data) {
|
||||||
|
if (data?.message) {
|
||||||
|
this.saveLog('error', data.message)
|
||||||
|
console.error('AbsLogger: error', data.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllLogs() {
|
||||||
|
return this.logs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AbsLogger = registerPlugin('AbsLogger', {
|
||||||
|
web: () => new AbsLoggerWeb()
|
||||||
|
})
|
||||||
|
|
||||||
|
export { AbsLogger }
|
||||||
@@ -2,12 +2,9 @@ import Vue from 'vue'
|
|||||||
import { AbsAudioPlayer } from './AbsAudioPlayer'
|
import { AbsAudioPlayer } from './AbsAudioPlayer'
|
||||||
import { AbsDownloader } from './AbsDownloader'
|
import { AbsDownloader } from './AbsDownloader'
|
||||||
import { AbsFileSystem } from './AbsFileSystem'
|
import { AbsFileSystem } from './AbsFileSystem'
|
||||||
|
import { AbsLogger } from './AbsLogger'
|
||||||
import { Capacitor } from '@capacitor/core'
|
import { Capacitor } from '@capacitor/core'
|
||||||
|
|
||||||
Vue.prototype.$platform = Capacitor.getPlatform()
|
Vue.prototype.$platform = Capacitor.getPlatform()
|
||||||
|
|
||||||
export {
|
export { AbsAudioPlayer, AbsDownloader, AbsFileSystem, AbsLogger }
|
||||||
AbsAudioPlayer,
|
|
||||||
AbsDownloader,
|
|
||||||
AbsFileSystem
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
"ButtonLatest": "Latest",
|
"ButtonLatest": "Latest",
|
||||||
"ButtonLibrary": "Library",
|
"ButtonLibrary": "Library",
|
||||||
"ButtonLocalMedia": "Local Media",
|
"ButtonLocalMedia": "Local Media",
|
||||||
|
"ButtonLogs": "Logs",
|
||||||
"ButtonManageLocalFiles": "Manage Local Files",
|
"ButtonManageLocalFiles": "Manage Local Files",
|
||||||
"ButtonNewFolder": "New Folder",
|
"ButtonNewFolder": "New Folder",
|
||||||
"ButtonNextEpisode": "Next Episode",
|
"ButtonNextEpisode": "Next Episode",
|
||||||
|
|||||||
Reference in New Issue
Block a user