feat(observability): add structured application logging

Emit correlated JSON logs from the API and FunASR services, propagate request IDs to the client, and configure bounded opt-in Docker logging.

Document the host-wide Grafana/Loki query workflow and keep observability deployment ownership outside the application repository.
This commit is contained in:
2026-07-22 13:52:40 +08:00
parent a63e9a1705
commit a875fe9f63
30 changed files with 1261 additions and 817 deletions

View File

@@ -1,4 +1,5 @@
import {
copyApiRequestId,
createFeedbackRecord,
createVoiceLesson,
ensureFeedbackSession,
@@ -6,6 +7,7 @@ import {
generateFeedbackFromVoice,
getActiveFeedbackSession,
getApiErrorMessage,
getApiRequestId,
listProfiles,
markVoiceLessonApplied,
retryVoiceSegment,
@@ -114,7 +116,8 @@ Page({
voiceStatus: '语音录入',
voiceActionable: false,
primaryActionText: '保存反馈',
errorMessage: ''
errorMessage: '',
errorRequestId: ''
},
onLoad() {
@@ -189,7 +192,7 @@ Page({
},
async loadProfiles() {
this.setData({ loading: true, errorMessage: '' })
this.setData({ loading: true, errorMessage: '', errorRequestId: '' })
try {
const profiles = await listProfiles()
const matchedProfileIndex = profiles.findIndex((profile) => profile.id === this.data.draft.profileId)
@@ -203,7 +206,7 @@ Page({
})
await this.loadActiveSession(profileId)
} catch (error) {
this.setData({ errorMessage: getApiErrorMessage(error) })
this.setData({ errorMessage: getApiErrorMessage(error), errorRequestId: getApiRequestId(error) })
} finally {
this.setData({ loading: false })
}
@@ -227,7 +230,7 @@ Page({
this.syncVoicePresentation(session)
this.scheduleVoicePolling(session)
} catch (error) {
this.setData({ errorMessage: getApiErrorMessage(error) })
this.setData({ errorMessage: getApiErrorMessage(error), errorRequestId: getApiRequestId(error) })
}
},
@@ -309,6 +312,10 @@ Page({
void this.loadProfiles()
},
copyRequestId() {
copyApiRequestId(this.data.errorRequestId)
},
onProfileChange(event: PickerEvent) {
this.clearDraftSaveTimer()
const selectedProfileIndex = Number(event.detail.value)