enh: log tail fixes and enhancements

This commit is contained in:
Ben Allfree 2023-10-19 15:28:46 -07:00
parent cbc5448d20
commit 08ae50e93f
8 changed files with 87 additions and 35 deletions

View File

@ -19,6 +19,7 @@
"dependencies": {
"@pockethost/common": "0.0.1",
"@swc/core": "^1.3.86",
"@types/tail": "^2.2.2",
"ajv": "^8.11.2",
"boolean": "^3.2.0",
"bottleneck": "^2.19.5",
@ -40,6 +41,7 @@
"semver": "^7.3.8",
"sqlite": "^4.1.2",
"sqlite3": "^5.1.6",
"tail": "^2.2.6",
"tmp": "^0.2.1",
"tsup": "^7.2.0",
"tsx": "^3.11.0",

View File

@ -1,5 +1,7 @@
import { mkInstanceDataPath } from '$constants'
import { mkInstanceDataPath, PUBLIC_DEBUG } from '$constants'
import { LoggerService } from '@pockethost/common'
import * as fs from 'fs'
import { Tail } from 'tail'
import * as winston from 'winston'
type UnsubFunc = () => void
@ -50,26 +52,38 @@ function createOrGetLogger(instanceId: string, target: string): winston.Logger {
export function InstanceLogger(instanceId: string, target: string) {
const logger = createOrGetLogger(instanceId, target)
return {
const api = {
info: (msg: string) => {
logger.info(msg)
},
error: (msg: string) => {
logger.error(msg)
},
tail: (linesBack: number, data: (line: string) => void): UnsubFunc => {
const stream = logger.stream({ start: -linesBack })
const listener = (log: winston.LogEntry) => {
data(JSON.stringify(log))
}
stream.on('log', listener)
tail: (
linesBack: number,
data: (line: winston.LogEntry) => void,
): UnsubFunc => {
const logFile = mkInstanceDataPath(instanceId, `logs`, `${target}.log`)
const tail = new Tail(logFile, { nLines: linesBack })
tail.on('line', (line) => {
const entry = JSON.parse(line)
data(entry)
})
// Return an unsubscribe function to remove the listener when done
return () => {
stream.removeListener('log', listener)
tail.unwatch()
}
},
}
if (PUBLIC_DEBUG) {
const { dbg } = LoggerService().create(`Logger:${instanceId}`)
api.tail(0, dbg)
}
return api
}
// // Example usage

View File

@ -277,6 +277,9 @@ export const instanceService = mkSingleton(
return cp
} catch (e) {
warn(`Error spawning: ${e}`)
userInstanceLogger.error(
`Could not launch PocketBase ${instance.version}. It may be time to upgrade.`,
)
throw new Error(
`Could not launch PocketBase ${instance.version}. It may be time to upgrade.`,
)

View File

@ -4,6 +4,7 @@ import {
mkInstanceDataPath,
PUBLIC_DEBUG,
} from '$constants'
import { port as getPort } from '$services'
import { assert, mkInternalUrl, tryFetch } from '$util'
import {
createCleanupManager,
@ -17,7 +18,6 @@ import {
import { map } from '@s-libs/micro-dash'
import Docker, { Container, ContainerCreateOptions } from 'dockerode'
import { existsSync } from 'fs'
import getPort from 'get-port'
import MemoryStream from 'memorystream'
import { dirname } from 'path'
import { gte } from 'semver'
@ -69,9 +69,15 @@ export const createPocketbaseService = async (
const _spawn = async (cfg: SpawnConfig, context?: AsyncContext) => {
const logger = (context?.logger || _serviceLogger).create('spawn')
const { dbg, warn, error } = logger
const defaultPort = await (async () => {
if (cfg.port) return cfg.port
const [defaultPort, freeDefaultPort] = await getPort.alloc()
cm.add(freeDefaultPort)
return defaultPort
})()
const _cfg: Required<SpawnConfig> = {
version: maxVersion,
port: cfg.port || (await getPort()),
port: defaultPort,
isMothership: false,
env: {},
stderr: new MemoryStream(),
@ -90,6 +96,8 @@ export const createPocketbaseService = async (
stderr,
stdout,
} = _cfg
const iLogger = InstanceLogger(slug, 'exec')
const _version = version || maxVersion // If _version is blank, we use the max version available
const realVersion = await getVersion(_version)
const binPath = realVersion.binPath
@ -128,13 +136,11 @@ export const createPocketbaseService = async (
let isRunning = true
const docker = new Docker()
const iLogger = InstanceLogger(slug, 'exec')
iLogger.info(`Starting instance`)
const _stdoutData = (data: Buffer) => {
const lines = data.toString().split(/\n/)
lines.forEach((line) => {
dbg(`${slug} stdout: ${line}`)
iLogger.info(line)
})
}
@ -142,7 +148,6 @@ export const createPocketbaseService = async (
const _stdErrData = (data: Buffer) => {
const lines = data.toString().split(/\n/)
lines.forEach((line) => {
warn(`${slug} stderr: ${line}`)
iLogger.error(line)
})
}
@ -178,7 +183,8 @@ export const createPocketbaseService = async (
[`8090/tcp`]: {},
},
}
dbg(`Spawning ${slug}`, { args, createOptions })
logger.info(`Spawning ${slug}`)
dbg({ args, createOptions })
let container: Container | undefined = undefined
const exited = new Promise<number>(async (resolveExit) => {
@ -191,16 +197,19 @@ export const createPocketbaseService = async (
createOptions,
(err, data) => {
const { StatusCode } = data || {}
dbg(`${slug} closed with code ${StatusCode}`, { err, data })
iLogger.info(`${slug} closed with code ${StatusCode}`)
dbg({ err, data })
isRunning = false
if (StatusCode > 0 || err) {
if (err?.json) {
error(`Error: ${err.json.message}`)
dbg(`${slug} stopped unexpectedly with code ${err}`, data)
}
iLogger.error(
`Unexpected stop with code ${StatusCode} and error ${err}`,
)
error(`${slug} stopped unexpectedly with code ${err}`, data)
onUnexpectedStop?.(StatusCode)
resolveExit(StatusCode || 999)
} else {
resolveExit(0)
}
resolveExit(0)
},
)
.on('container', (container: Container) => {
@ -208,14 +217,29 @@ export const createPocketbaseService = async (
resolve(container)
})
})
cm.add(async () => {
dbg(`Stopping ${slug} for cleanup`)
await container
?.stop()
.catch((err) => warn(`Possible error stopping container: ${err}`))
stderr.off('data', _stdErrData)
stdout.off('data', _stdoutData)
})
if (container) {
cm.add(async () => {
dbg(`Stopping ${slug} for cleanup`)
iLogger.info(`Stopping instance`)
await container
?.stop()
.then(() => {
iLogger.info(`Instance stopped`)
})
.catch((err) => {
iLogger.error(`Error stopping instance`)
warn(`Possible error stopping container: ${err}`)
})
stderr.off('data', _stdErrData)
stdout.off('data', _stdoutData)
})
} else {
iLogger.error(`Could not start container`)
error(`${slug} could not start container`)
onUnexpectedStop?.(999)
resolveExit(999)
}
})
const url = mkInternalUrl(port)

View File

@ -107,10 +107,8 @@ export const realtimeLog = mkSingleton(async (config: RealtimeLogConfig) => {
'Cache-Control': 'no-store',
})
const unsub = instanceLogger.tail(100, (line) => {
const obj = JSON.parse(line)
const evt = mkEvent(`log`, obj)
dbg(`****sending ${evt}`)
const unsub = instanceLogger.tail(100, (entry) => {
const evt = mkEvent(`log`, entry)
res.write(evt)
})

View File

@ -1,6 +1,7 @@
export * from './FtpService/FtpService'
export * from './InstanceService/InstanceService'
export * from './PocketBaseService/PocketBaseService'
export * from './PortManager'
export * from './ProxyService'
export * from './RealtimeLog'
export * from './RpcService/RpcService'

View File

@ -79,7 +79,7 @@
<div class="modal-box max-w-[90vw] h-[90vh]">
<h3 class="font-bold text-lg">Instance Logging</h3>
<div class="py-4 h-[80vh] overflow-y-scroll flex flex-col-reverse gap-3">
<div class="py-4 h-[80vh] overflow-y-scroll flex flex-col gap-3">
{#each $logs as log}
<div
class="px-4 text-[11px] font-mono flex align-center"
@ -112,7 +112,7 @@
on:click={handleFullScreenModal}
>Fullscreen <i class="fa-regular fa-arrows-maximize"></i></button
>
<div class="h-[450px] flex flex-col-reverse overflow-y-scroll gap-3">
<div class="h-[450px] flex flex-col overflow-y-scroll gap-3">
{#each $logs as log}
<div
class="px-4 text-[11px] font-mono flex align-center"

View File

@ -843,6 +843,11 @@
dependencies:
"@types/node" "^18.11.18"
"@types/tail@^2.2.2":
version "2.2.2"
resolved "https://registry.yarnpkg.com/@types/tail/-/tail-2.2.2.tgz#3b59bcd0bc91aa2cf50838fcbc62d1071a3ab72d"
integrity sha512-+CjjgMFjIVgTYsJXWNpAKVRerFWc9c+GTMzY/336fSW6BhY5TJwo2CNYJiNq7mO9rBHmtmpceKf2DnkrnaR3Vg==
"@types/tmp@^0.2.1":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165"
@ -6301,6 +6306,11 @@ swap-case@^1.1.0:
lower-case "^1.1.1"
upper-case "^1.1.1"
tail@^2.2.6:
version "2.2.6"
resolved "https://registry.yarnpkg.com/tail/-/tail-2.2.6.tgz#24abd701963639b896c42496d5f416216ec0b558"
integrity sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw==
tailwindcss@^3, tailwindcss@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"