Skip to content

Commit af4cef6

Browse files
committed
fix(logger): optimize logger memory management and reduce high-frequency logging (#156)
- Reduce Logger export history from 10k to 1k entries and add automatic cleanup - Implement lightweight serialization to replace deep serialization - Add sampling strategy for high-frequency logs (time_update, MediaClock events) - Disable export history completely in production environment - Optimize MediaClock logging with 5-10% sampling for duplicates and debug messages - Reduce PlayerOrchestrator trace buffer from 200 to 50 entries - Remove high-frequency onTimeUpdate silly logs - Implement stricter production log levels (WARN+ for renderer, ERROR+ for main process) This should significantly reduce memory pressure from logger-related objects as identified in Chrome DevTools memory analysis.
1 parent 5045477 commit af4cef6

3 files changed

Lines changed: 165 additions & 84 deletions

File tree

src/renderer/src/pages/player/engine/MediaClock.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,16 @@ class EventDeduplicator {
214214

215215
// 使用 TimeMath 进行时间相等性检查
216216
if (event.type === 'time_update') {
217-
// 检查是否为相同时间点(在容差范围内)
217+
// 检查是否为相同时间点(在容差范围内) - 减少重复日志
218218
if (TimeMath.equals(existing.currentTime, event.currentTime)) {
219-
logger.debug('Duplicate time_update event (TimeMath.equals)', {
220-
existing: existing.currentTime,
221-
current: event.currentTime,
222-
epsilon: TimeMath.EPS
223-
})
219+
// 降低重复事件日志频率,只记录采样的重复事件
220+
if (Math.random() < 0.05) {
221+
logger.debug('Duplicate time_update event (TimeMath.equals - sampled)', {
222+
existing: existing.currentTime,
223+
current: event.currentTime,
224+
epsilon: TimeMath.EPS
225+
})
226+
}
224227
return true
225228
}
226229

@@ -232,11 +235,14 @@ class EventDeduplicator {
232235
const boundaries = [0, existing.currentTime, event.currentTime] // 可能的边界点
233236
for (const boundary of boundaries) {
234237
if (TimeMath.detectBoundaryFlutter(this.timeHistory, boundary)) {
235-
logger.debug('Boundary flutter detected, treating as duplicate', {
236-
boundary,
237-
timeHistory: this.timeHistory.slice(0, 3),
238-
currentTime: event.currentTime
239-
})
238+
// 减少边界抖动检测日志频率
239+
if (Math.random() < 0.1) {
240+
logger.debug('Boundary flutter detected, treating as duplicate (sampled)', {
241+
boundary,
242+
timeHistory: this.timeHistory.slice(0, 3),
243+
currentTime: event.currentTime
244+
})
245+
}
240246
return true
241247
}
242248
}
@@ -608,9 +614,9 @@ export class MediaClock {
608614
playbackRate: this.state.playbackRate
609615
}
610616

611-
// 在高精度模式下添加额外的调试信息
612-
if (this.throttler.getMode() === ThrottleMode.HIGH_PRECISION) {
613-
logger.debug('High-precision time update', {
617+
// 在高精度模式下添加额外的调试信息 - 降低频率
618+
if (this.throttler.getMode() === ThrottleMode.HIGH_PRECISION && Math.random() < 0.1) {
619+
logger.debug('High-precision time update (sampled)', {
614620
previousTime,
615621
currentTime,
616622
epsilon,

src/renderer/src/pages/player/engine/PlayerOrchestrator.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,8 @@ export class PlayerOrchestrator {
572572

573573
onTimeUpdate(currentTime: number): void {
574574
this.mediaClock.updateTime(currentTime)
575-
logger.silly('onTimeUpdate', { currentTime })
575+
// 移除高频的 silly 日志,减少内存压力
576+
// logger.silly('onTimeUpdate', { currentTime })
576577
}
577578

578579
onPlay(): void {
@@ -1214,9 +1215,9 @@ export class PlayerOrchestrator {
12141215
}
12151216
}
12161217

1217-
// 存储追踪记录(最多 200 条
1218+
// 存储追踪记录(减少到最多 50 条,优化内存使用
12181219
this._traceBuf.push(traceRecord)
1219-
if (this._traceBuf.length > 200) this._traceBuf.shift()
1220+
if (this._traceBuf.length > 50) this._traceBuf.shift()
12201221

12211222
// 根据配置输出日志
12221223
if (this.config.enableDebugLogs) {

src/renderer/src/services/Logger.ts

Lines changed: 141 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ const IS_WORKER = typeof window === 'undefined'
88
// DO NOT use `constants.ts` here, because the files contains other dependencies that will fail in worker process
99
const IS_DEV = IS_WORKER ? false : window.electron?.process?.env?.NODE_ENV === 'development'
1010

11-
const DEFAULT_LEVEL = IS_DEV ? LEVEL.SILLY : LEVEL.INFO
12-
const MAIN_LOG_LEVEL = LEVEL.WARN
11+
// 更严格的生产环境日志级别控制
12+
const DEFAULT_LEVEL = IS_DEV ? LEVEL.DEBUG : LEVEL.WARN // 生产环境默认只记录警告以上
13+
const MAIN_LOG_LEVEL = IS_DEV ? LEVEL.WARN : LEVEL.ERROR // 生产环境主进程只记录错误
1314

1415
// 日志导出相关接口
1516
interface LogExportEntry {
@@ -54,9 +55,12 @@ class LoggerService {
5455
private module: string = ''
5556
private context: Record<string, any> = {}
5657

57-
// 日志导出相关
58+
// 日志导出相关 - 优化内存管理
5859
private exportHistory: LogExportEntry[] = []
59-
private maxHistorySize: number = 10000
60+
private maxHistorySize: number = 1000 // 降低到 1000 条记录,减少内存占用
61+
private lastCleanupTime: number = 0
62+
private readonly CLEANUP_INTERVAL = 30000 // 30秒清理一次
63+
private readonly MEMORY_PRESSURE_THRESHOLD = 500 // 内存压力阈值
6064

6165
private constructor() {
6266
if (IS_DEV) {
@@ -134,100 +138,82 @@ class LoggerService {
134138
return newLogger
135139
}
136140

137-
// ---- 对象完全序列化方法 ----
138-
private deepSerialize(obj: any, visited = new WeakSet()): any {
141+
// ---- 轻量化序列化方法 - 优化内存使用 ----
142+
private lightSerialize(obj: any, maxDepth = 3, currentDepth = 0): any {
143+
// 超过最大深度,简化处理
144+
if (currentDepth >= maxDepth) {
145+
if (obj === null || obj === undefined) return obj
146+
if (typeof obj === 'string') return obj.length > 100 ? obj.slice(0, 100) + '...' : obj
147+
if (typeof obj === 'number' || typeof obj === 'boolean') return obj
148+
return '[Max Depth Reached]'
149+
}
150+
139151
// 处理基本类型
140152
if (obj === null || typeof obj !== 'object') {
141153
return obj
142154
}
143155

144-
// 防止循环引用
145-
if (visited.has(obj)) {
146-
return '[Circular Reference]'
147-
}
148-
visited.add(obj)
149-
150156
try {
151157
// 处理日期对象
152158
if (obj instanceof Date) {
153-
return { __type: 'Date', value: obj.toISOString() }
159+
return obj.toISOString()
154160
}
155161

156-
// 处理错误对象
162+
// 处理错误对象 - 简化
157163
if (obj instanceof Error) {
158164
return {
159165
__type: 'Error',
160166
name: obj.name,
161167
message: obj.message,
162-
stack: obj.stack,
163-
...Object.getOwnPropertyNames(obj).reduce((acc, key) => {
164-
acc[key] = this.deepSerialize((obj as any)[key], visited)
165-
return acc
166-
}, {} as any)
168+
stack: obj.stack?.split('\n').slice(0, 3).join('\n') // 只保留前3行堆栈
167169
}
168170
}
169171

170-
// 处理函数
172+
// 处理函数 - 极简化
171173
if (typeof obj === 'function') {
172-
return {
173-
__type: 'Function',
174-
name: obj.name,
175-
toString: obj.toString()
176-
}
174+
return `[Function: ${obj.name || 'anonymous'}]`
177175
}
178176

179-
// 处理数组
177+
// 处理数组 - 限制长度
180178
if (Array.isArray(obj)) {
181-
return obj.map((item) => this.deepSerialize(item, visited))
179+
const maxItems = 10
180+
if (obj.length > maxItems) {
181+
return [
182+
...obj
183+
.slice(0, maxItems)
184+
.map((item) => this.lightSerialize(item, maxDepth, currentDepth + 1)),
185+
`[... ${obj.length - maxItems} more items]`
186+
]
187+
}
188+
return obj.map((item) => this.lightSerialize(item, maxDepth, currentDepth + 1))
182189
}
183190

184-
// 处理普通对象
191+
// 处理普通对象 - 只序列化可枚举属性
185192
const result: any = {}
193+
const keys = Object.keys(obj)
194+
const maxKeys = 20 // 限制最多序列化20个属性
186195

187-
// 获取所有属性(包括不可枚举的)
188-
const keys = [
189-
...Object.keys(obj),
190-
...Object.getOwnPropertyNames(obj).filter(
191-
(key) => key !== 'constructor' && !Object.keys(obj).includes(key)
192-
)
193-
]
194-
195-
for (const key of keys) {
196+
for (let i = 0; i < Math.min(keys.length, maxKeys); i++) {
197+
const key = keys[i]
196198
try {
197-
const descriptor = Object.getOwnPropertyDescriptor(obj, key)
198-
if (descriptor) {
199-
if (descriptor.get || descriptor.set) {
200-
// 处理 getter/setter
201-
result[key] = {
202-
__type: 'Property',
203-
hasGetter: !!descriptor.get,
204-
hasSetter: !!descriptor.set,
205-
enumerable: descriptor.enumerable,
206-
configurable: descriptor.configurable
207-
}
208-
} else {
209-
// 普通属性
210-
result[key] = this.deepSerialize(descriptor.value, visited)
211-
}
199+
// 跳过可能导致循环引用的属性
200+
if (key.includes('parent') || key.includes('owner') || key.includes('target')) {
201+
result[key] = '[Skipped - Potential Circular]'
202+
continue
212203
}
204+
result[key] = this.lightSerialize(obj[key], maxDepth, currentDepth + 1)
213205
} catch (error) {
214-
result[key] = `[Error accessing property: ${
215-
error instanceof Error ? error.message : 'Unknown error'
216-
}]`
206+
result[key] = '[Serialization Error]'
217207
}
218208
}
219209

220-
// 添加原型信息
221-
const proto = Object.getPrototypeOf(obj)
222-
if (proto && proto !== Object.prototype) {
223-
result.__prototype = proto.constructor?.name || '[Unknown Prototype]'
210+
if (keys.length > maxKeys) {
211+
result['[...]'] = `${keys.length - maxKeys} more properties`
224212
}
225213

226214
return result
227215
} catch (error) {
228-
return `[Serialization Error: ${error instanceof Error ? error.message : 'Unknown error'}]`
229-
} finally {
230-
visited.delete(obj)
216+
return '[Serialization Error]'
231217
}
232218
}
233219

@@ -520,33 +506,121 @@ class LoggerService {
520506

521507
// ---- 日志导出方法 ----
522508
/**
523-
* 添加日志到导出历史
509+
* 添加日志到导出历史 - 优化内存管理,生产环境禁用
524510
*/
525511
private addToExportHistory(
526512
level: LogLevel,
527513
message: string,
528514
data: any[],
529515
caller?: string | null
530516
): void {
517+
// 生产环境完全禁用导出历史功能,节省内存
518+
if (!IS_DEV) {
519+
return
520+
}
521+
522+
// 检查是否需要清理
523+
this.checkAndCleanupHistory()
524+
525+
// 对于高频日志,采用采样策略
526+
if (this.shouldSkipForSampling(level, message)) {
527+
return
528+
}
529+
531530
const entry: LogExportEntry = {
532531
timestamp: new Date().toISOString(),
533532
level,
534533
module: this.module,
535534
window: this.window,
536535
message,
537-
data: data.length > 0 ? data.map((item) => this.deepSerialize(item)) : undefined,
538-
context: Object.keys(this.context).length > 0 ? this.deepSerialize(this.context) : undefined,
536+
data: data.length > 0 ? data.map((item) => this.lightSerialize(item, 2)) : undefined,
537+
context:
538+
Object.keys(this.context).length > 0 ? this.lightSerialize(this.context, 2) : undefined,
539539
caller: caller || undefined
540540
}
541541

542542
this.exportHistory.push(entry)
543543

544-
// 限制历史记录数量
544+
// 更积极的内存管理
545545
if (this.exportHistory.length > this.maxHistorySize) {
546-
this.exportHistory = this.exportHistory.slice(-this.maxHistorySize)
546+
// 保留最近的记录,删除最旧的
547+
const keepCount = Math.floor(this.maxHistorySize * 0.8) // 保留80%
548+
this.exportHistory.splice(0, this.exportHistory.length - keepCount)
547549
}
548550
}
549551

552+
/**
553+
* 采样策略 - 对高频日志进行采样
554+
*/
555+
private shouldSkipForSampling(level: LogLevel, message: string): boolean {
556+
// 对于 SILLY 和 DEBUG 级别的高频消息进行采样
557+
if (level === LEVEL.SILLY || level === LEVEL.DEBUG) {
558+
// 检查是否为高频消息模式
559+
const isHighFrequency =
560+
message.includes('time_update') ||
561+
message.includes('MediaClock') ||
562+
message.includes('throttle') ||
563+
message.includes('performance')
564+
565+
if (isHighFrequency) {
566+
// 只保留每10条记录中的1条
567+
return Math.random() > 0.1
568+
}
569+
}
570+
return false
571+
}
572+
573+
/**
574+
* 检查并清理历史记录
575+
*/
576+
private checkAndCleanupHistory(): void {
577+
const now = Date.now()
578+
579+
// 定期清理
580+
if (now - this.lastCleanupTime > this.CLEANUP_INTERVAL) {
581+
this.performCleanup()
582+
this.lastCleanupTime = now
583+
}
584+
585+
// 内存压力检测
586+
if (this.exportHistory.length > this.MEMORY_PRESSURE_THRESHOLD) {
587+
this.performEmergencyCleanup()
588+
}
589+
}
590+
591+
/**
592+
* 执行常规清理
593+
*/
594+
private performCleanup(): void {
595+
const oldLength = this.exportHistory.length
596+
597+
// 清理30分钟前的记录
598+
const thirtyMinutesAgo = Date.now() - 30 * 60 * 1000
599+
this.exportHistory = this.exportHistory.filter((entry) => {
600+
const entryTime = new Date(entry.timestamp).getTime()
601+
return entryTime > thirtyMinutesAgo
602+
})
603+
604+
if (oldLength !== this.exportHistory.length) {
605+
console.log(`[LoggerService] 清理了 ${oldLength - this.exportHistory.length} 条过期日志记录`)
606+
}
607+
}
608+
609+
/**
610+
* 执行紧急清理
611+
*/
612+
private performEmergencyCleanup(): void {
613+
const oldLength = this.exportHistory.length
614+
615+
// 只保留最近的记录
616+
const keepCount = Math.floor(this.maxHistorySize * 0.5)
617+
this.exportHistory.splice(0, this.exportHistory.length - keepCount)
618+
619+
console.warn(
620+
`[LoggerService] 内存压力过大,执行紧急清理,删除了 ${oldLength - this.exportHistory.length} 条记录`
621+
)
622+
}
623+
550624
/**
551625
* 导出日志
552626
*/

0 commit comments

Comments
 (0)