fix: log handle leak

This commit is contained in:
Ben Allfree 2024-01-28 14:32:37 +00:00
parent 1f5c86a7b2
commit f9f0a4f391
3 changed files with 61 additions and 22 deletions

View File

@ -37,7 +37,7 @@ server.on('message', (msg, rinfo) => {
const { process: instanceId, severity, message } = parsed const { process: instanceId, severity, message } = parsed
const logger = InstanceLogger(instanceId, `exec`) const logger = InstanceLogger(instanceId, `exec`, { ttl: 5000 })
if (severity === 'info') { if (severity === 'info') {
logger.info(message) logger.info(message)
} else { } else {

View File

@ -1,37 +1,53 @@
import { DEBUG, mkInstanceDataPath } from '$constants' import { mkInstanceDataPath } from '$constants'
import { LoggerService, createCleanupManager } from '$shared' import { createCleanupManager, LoggerService } from '$shared'
import { asyncExitHook } from '$util' import { asyncExitHook, mergeConfig } from '$util'
import * as fs from 'fs' import * as fs from 'fs'
import { Tail } from 'tail' import { Tail } from 'tail'
import * as winston from 'winston' import * as winston from 'winston'
type UnsubFunc = () => void type UnsubFunc = () => void
export type InstanceLoggerApi = {
info: (msg: string) => void
error: (msg: string) => void
tail: (linesBack: number, data: (line: winston.LogEntry) => void) => UnsubFunc
shutdown: () => void
}
export type InstanceLoggerOptions = {
ttl: number
}
const loggers: { const loggers: {
[key: string]: { [key: string]: InstanceLoggerApi
info: (msg: string) => void
error: (msg: string) => void
tail: (
linesBack: number,
data: (line: winston.LogEntry) => void,
) => UnsubFunc
}
} = {} } = {}
export function InstanceLogger(instanceId: string, target: string) { export function InstanceLogger(
instanceId: string,
target: string,
options: Partial<InstanceLoggerOptions> = {},
) {
const { dbg, info } = LoggerService().create(instanceId).breadcrumb(target)
const { ttl } = mergeConfig<InstanceLoggerOptions>({ ttl: 0 }, options)
dbg({ ttl })
const loggerKey = `${instanceId}_${target}` const loggerKey = `${instanceId}_${target}`
if (loggers[loggerKey]) { if (loggers[loggerKey]) {
dbg(`Logger exists, using cache`)
return loggers[loggerKey]! return loggers[loggerKey]!
} }
const logDirectory = mkInstanceDataPath(instanceId, `logs`) const logDirectory = mkInstanceDataPath(instanceId, `logs`)
if (!fs.existsSync(logDirectory)) { if (!fs.existsSync(logDirectory)) {
console.log(`Creating ${logDirectory}`) dbg(`Creating ${logDirectory}`)
fs.mkdirSync(logDirectory, { recursive: true }) fs.mkdirSync(logDirectory, { recursive: true })
} }
const logFile = mkInstanceDataPath(instanceId, `logs`, `${target}.log`) const logFile = mkInstanceDataPath(instanceId, `logs`, `${target}.log`)
const cm = createCleanupManager()
const logger = winston.createLogger({ const logger = winston.createLogger({
format: winston.format.combine( format: winston.format.combine(
winston.format.timestamp(), winston.format.timestamp(),
@ -55,25 +71,50 @@ export function InstanceLogger(instanceId: string, target: string) {
], ],
}) })
cm.add(() => {
dbg(`Deleting and closing`)
delete loggers[loggerKey]
logger.close()
})
const { error, warn } = LoggerService() const { error, warn } = LoggerService()
.create('InstanceLogger') .create('InstanceLogger')
.breadcrumb(instanceId) .breadcrumb(instanceId)
.breadcrumb(target) .breadcrumb(target)
const resetTtl = (() => {
let tid: ReturnType<typeof setTimeout>
return () => {
if (!ttl) return
clearTimeout(tid)
tid = setTimeout(() => {
dbg(`Logger timeout`)
api.shutdown()
}, ttl)
}
})()
const api = { const api = {
info: (msg: string) => { info: (msg: string) => {
resetTtl()
dbg(`info: `, msg)
logger.info(msg) logger.info(msg)
}, },
error: (msg: string) => { error: (msg: string) => {
resetTtl()
dbg(`error: `, msg)
logger.error(msg) logger.error(msg)
}, },
tail: ( tail: (
linesBack: number, linesBack: number,
data: (line: winston.LogEntry) => void, data: (line: winston.LogEntry) => void,
): UnsubFunc => { ): UnsubFunc => {
if (ttl) {
throw new Error(`Cannot tail with ttl active`)
}
const logFile = mkInstanceDataPath(instanceId, `logs`, `${target}.log`) const logFile = mkInstanceDataPath(instanceId, `logs`, `${target}.log`)
const cm = createCleanupManager()
let tid: any let tid: any
cm.add(() => clearTimeout(tid)) cm.add(() => clearTimeout(tid))
const check = () => { const check = () => {
@ -111,12 +152,7 @@ export function InstanceLogger(instanceId: string, target: string) {
unsub() unsub()
} }
}, },
} shutdown: () => cm.shutdown(),
if (DEBUG()) {
const { dbg } = LoggerService().create(`Logger`).breadcrumb(instanceId)
api.tail(0, (entry) => {
dbg(entry.message)
})
} }
loggers[loggerKey] = api loggers[loggerKey] = api

View File

@ -80,7 +80,10 @@ export const realtimeLog = mkSingleton(async (config: RealtimeLogConfig) => {
res.write(evt) res.write(evt)
}) })
res.on('close', unsub) res.on('close', () => {
unsub()
instanceLogger.shutdown()
})
}) })
return {} return {}