mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
enh: log tail fixes and enhancements
This commit is contained in:
parent
cbc5448d20
commit
08ae50e93f
@ -19,6 +19,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pockethost/common": "0.0.1",
|
"@pockethost/common": "0.0.1",
|
||||||
"@swc/core": "^1.3.86",
|
"@swc/core": "^1.3.86",
|
||||||
|
"@types/tail": "^2.2.2",
|
||||||
"ajv": "^8.11.2",
|
"ajv": "^8.11.2",
|
||||||
"boolean": "^3.2.0",
|
"boolean": "^3.2.0",
|
||||||
"bottleneck": "^2.19.5",
|
"bottleneck": "^2.19.5",
|
||||||
@ -40,6 +41,7 @@
|
|||||||
"semver": "^7.3.8",
|
"semver": "^7.3.8",
|
||||||
"sqlite": "^4.1.2",
|
"sqlite": "^4.1.2",
|
||||||
"sqlite3": "^5.1.6",
|
"sqlite3": "^5.1.6",
|
||||||
|
"tail": "^2.2.6",
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"tsup": "^7.2.0",
|
"tsup": "^7.2.0",
|
||||||
"tsx": "^3.11.0",
|
"tsx": "^3.11.0",
|
||||||
|
@ -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 * as fs from 'fs'
|
||||||
|
import { Tail } from 'tail'
|
||||||
import * as winston from 'winston'
|
import * as winston from 'winston'
|
||||||
|
|
||||||
type UnsubFunc = () => void
|
type UnsubFunc = () => void
|
||||||
@ -50,26 +52,38 @@ function createOrGetLogger(instanceId: string, target: string): winston.Logger {
|
|||||||
export function InstanceLogger(instanceId: string, target: string) {
|
export function InstanceLogger(instanceId: string, target: string) {
|
||||||
const logger = createOrGetLogger(instanceId, target)
|
const logger = createOrGetLogger(instanceId, target)
|
||||||
|
|
||||||
return {
|
const api = {
|
||||||
info: (msg: string) => {
|
info: (msg: string) => {
|
||||||
logger.info(msg)
|
logger.info(msg)
|
||||||
},
|
},
|
||||||
error: (msg: string) => {
|
error: (msg: string) => {
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
},
|
},
|
||||||
tail: (linesBack: number, data: (line: string) => void): UnsubFunc => {
|
tail: (
|
||||||
const stream = logger.stream({ start: -linesBack })
|
linesBack: number,
|
||||||
const listener = (log: winston.LogEntry) => {
|
data: (line: winston.LogEntry) => void,
|
||||||
data(JSON.stringify(log))
|
): UnsubFunc => {
|
||||||
}
|
const logFile = mkInstanceDataPath(instanceId, `logs`, `${target}.log`)
|
||||||
stream.on('log', listener)
|
|
||||||
|
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 an unsubscribe function to remove the listener when done
|
||||||
return () => {
|
return () => {
|
||||||
stream.removeListener('log', listener)
|
tail.unwatch()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
if (PUBLIC_DEBUG) {
|
||||||
|
const { dbg } = LoggerService().create(`Logger:${instanceId}`)
|
||||||
|
api.tail(0, dbg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Example usage
|
// // Example usage
|
||||||
|
@ -277,6 +277,9 @@ export const instanceService = mkSingleton(
|
|||||||
return cp
|
return cp
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
warn(`Error spawning: ${e}`)
|
warn(`Error spawning: ${e}`)
|
||||||
|
userInstanceLogger.error(
|
||||||
|
`Could not launch PocketBase ${instance.version}. It may be time to upgrade.`,
|
||||||
|
)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not launch PocketBase ${instance.version}. It may be time to upgrade.`,
|
`Could not launch PocketBase ${instance.version}. It may be time to upgrade.`,
|
||||||
)
|
)
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
mkInstanceDataPath,
|
mkInstanceDataPath,
|
||||||
PUBLIC_DEBUG,
|
PUBLIC_DEBUG,
|
||||||
} from '$constants'
|
} from '$constants'
|
||||||
|
import { port as getPort } from '$services'
|
||||||
import { assert, mkInternalUrl, tryFetch } from '$util'
|
import { assert, mkInternalUrl, tryFetch } from '$util'
|
||||||
import {
|
import {
|
||||||
createCleanupManager,
|
createCleanupManager,
|
||||||
@ -17,7 +18,6 @@ import {
|
|||||||
import { map } from '@s-libs/micro-dash'
|
import { map } from '@s-libs/micro-dash'
|
||||||
import Docker, { Container, ContainerCreateOptions } from 'dockerode'
|
import Docker, { Container, ContainerCreateOptions } from 'dockerode'
|
||||||
import { existsSync } from 'fs'
|
import { existsSync } from 'fs'
|
||||||
import getPort from 'get-port'
|
|
||||||
import MemoryStream from 'memorystream'
|
import MemoryStream from 'memorystream'
|
||||||
import { dirname } from 'path'
|
import { dirname } from 'path'
|
||||||
import { gte } from 'semver'
|
import { gte } from 'semver'
|
||||||
@ -69,9 +69,15 @@ export const createPocketbaseService = async (
|
|||||||
const _spawn = async (cfg: SpawnConfig, context?: AsyncContext) => {
|
const _spawn = async (cfg: SpawnConfig, context?: AsyncContext) => {
|
||||||
const logger = (context?.logger || _serviceLogger).create('spawn')
|
const logger = (context?.logger || _serviceLogger).create('spawn')
|
||||||
const { dbg, warn, error } = logger
|
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> = {
|
const _cfg: Required<SpawnConfig> = {
|
||||||
version: maxVersion,
|
version: maxVersion,
|
||||||
port: cfg.port || (await getPort()),
|
port: defaultPort,
|
||||||
isMothership: false,
|
isMothership: false,
|
||||||
env: {},
|
env: {},
|
||||||
stderr: new MemoryStream(),
|
stderr: new MemoryStream(),
|
||||||
@ -90,6 +96,8 @@ export const createPocketbaseService = async (
|
|||||||
stderr,
|
stderr,
|
||||||
stdout,
|
stdout,
|
||||||
} = _cfg
|
} = _cfg
|
||||||
|
const iLogger = InstanceLogger(slug, 'exec')
|
||||||
|
|
||||||
const _version = version || maxVersion // If _version is blank, we use the max version available
|
const _version = version || maxVersion // If _version is blank, we use the max version available
|
||||||
const realVersion = await getVersion(_version)
|
const realVersion = await getVersion(_version)
|
||||||
const binPath = realVersion.binPath
|
const binPath = realVersion.binPath
|
||||||
@ -128,13 +136,11 @@ export const createPocketbaseService = async (
|
|||||||
let isRunning = true
|
let isRunning = true
|
||||||
|
|
||||||
const docker = new Docker()
|
const docker = new Docker()
|
||||||
const iLogger = InstanceLogger(slug, 'exec')
|
|
||||||
iLogger.info(`Starting instance`)
|
iLogger.info(`Starting instance`)
|
||||||
|
|
||||||
const _stdoutData = (data: Buffer) => {
|
const _stdoutData = (data: Buffer) => {
|
||||||
const lines = data.toString().split(/\n/)
|
const lines = data.toString().split(/\n/)
|
||||||
lines.forEach((line) => {
|
lines.forEach((line) => {
|
||||||
dbg(`${slug} stdout: ${line}`)
|
|
||||||
iLogger.info(line)
|
iLogger.info(line)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -142,7 +148,6 @@ export const createPocketbaseService = async (
|
|||||||
const _stdErrData = (data: Buffer) => {
|
const _stdErrData = (data: Buffer) => {
|
||||||
const lines = data.toString().split(/\n/)
|
const lines = data.toString().split(/\n/)
|
||||||
lines.forEach((line) => {
|
lines.forEach((line) => {
|
||||||
warn(`${slug} stderr: ${line}`)
|
|
||||||
iLogger.error(line)
|
iLogger.error(line)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -178,7 +183,8 @@ export const createPocketbaseService = async (
|
|||||||
[`8090/tcp`]: {},
|
[`8090/tcp`]: {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
dbg(`Spawning ${slug}`, { args, createOptions })
|
logger.info(`Spawning ${slug}`)
|
||||||
|
dbg({ args, createOptions })
|
||||||
|
|
||||||
let container: Container | undefined = undefined
|
let container: Container | undefined = undefined
|
||||||
const exited = new Promise<number>(async (resolveExit) => {
|
const exited = new Promise<number>(async (resolveExit) => {
|
||||||
@ -191,16 +197,19 @@ export const createPocketbaseService = async (
|
|||||||
createOptions,
|
createOptions,
|
||||||
(err, data) => {
|
(err, data) => {
|
||||||
const { StatusCode } = data || {}
|
const { StatusCode } = data || {}
|
||||||
dbg(`${slug} closed with code ${StatusCode}`, { err, data })
|
iLogger.info(`${slug} closed with code ${StatusCode}`)
|
||||||
|
dbg({ err, data })
|
||||||
isRunning = false
|
isRunning = false
|
||||||
if (StatusCode > 0 || err) {
|
if (StatusCode > 0 || err) {
|
||||||
if (err?.json) {
|
iLogger.error(
|
||||||
error(`Error: ${err.json.message}`)
|
`Unexpected stop with code ${StatusCode} and error ${err}`,
|
||||||
dbg(`${slug} stopped unexpectedly with code ${err}`, data)
|
)
|
||||||
}
|
error(`${slug} stopped unexpectedly with code ${err}`, data)
|
||||||
onUnexpectedStop?.(StatusCode)
|
onUnexpectedStop?.(StatusCode)
|
||||||
|
resolveExit(StatusCode || 999)
|
||||||
|
} else {
|
||||||
|
resolveExit(0)
|
||||||
}
|
}
|
||||||
resolveExit(0)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.on('container', (container: Container) => {
|
.on('container', (container: Container) => {
|
||||||
@ -208,14 +217,29 @@ export const createPocketbaseService = async (
|
|||||||
resolve(container)
|
resolve(container)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
cm.add(async () => {
|
if (container) {
|
||||||
dbg(`Stopping ${slug} for cleanup`)
|
cm.add(async () => {
|
||||||
await container
|
dbg(`Stopping ${slug} for cleanup`)
|
||||||
?.stop()
|
iLogger.info(`Stopping instance`)
|
||||||
.catch((err) => warn(`Possible error stopping container: ${err}`))
|
await container
|
||||||
stderr.off('data', _stdErrData)
|
?.stop()
|
||||||
stdout.off('data', _stdoutData)
|
.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)
|
const url = mkInternalUrl(port)
|
||||||
|
@ -107,10 +107,8 @@ export const realtimeLog = mkSingleton(async (config: RealtimeLogConfig) => {
|
|||||||
'Cache-Control': 'no-store',
|
'Cache-Control': 'no-store',
|
||||||
})
|
})
|
||||||
|
|
||||||
const unsub = instanceLogger.tail(100, (line) => {
|
const unsub = instanceLogger.tail(100, (entry) => {
|
||||||
const obj = JSON.parse(line)
|
const evt = mkEvent(`log`, entry)
|
||||||
const evt = mkEvent(`log`, obj)
|
|
||||||
dbg(`****sending ${evt}`)
|
|
||||||
res.write(evt)
|
res.write(evt)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export * from './FtpService/FtpService'
|
export * from './FtpService/FtpService'
|
||||||
export * from './InstanceService/InstanceService'
|
export * from './InstanceService/InstanceService'
|
||||||
export * from './PocketBaseService/PocketBaseService'
|
export * from './PocketBaseService/PocketBaseService'
|
||||||
|
export * from './PortManager'
|
||||||
export * from './ProxyService'
|
export * from './ProxyService'
|
||||||
export * from './RealtimeLog'
|
export * from './RealtimeLog'
|
||||||
export * from './RpcService/RpcService'
|
export * from './RpcService/RpcService'
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
<div class="modal-box max-w-[90vw] h-[90vh]">
|
<div class="modal-box max-w-[90vw] h-[90vh]">
|
||||||
<h3 class="font-bold text-lg">Instance Logging</h3>
|
<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}
|
{#each $logs as log}
|
||||||
<div
|
<div
|
||||||
class="px-4 text-[11px] font-mono flex align-center"
|
class="px-4 text-[11px] font-mono flex align-center"
|
||||||
@ -112,7 +112,7 @@
|
|||||||
on:click={handleFullScreenModal}
|
on:click={handleFullScreenModal}
|
||||||
>Fullscreen <i class="fa-regular fa-arrows-maximize"></i></button
|
>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}
|
{#each $logs as log}
|
||||||
<div
|
<div
|
||||||
class="px-4 text-[11px] font-mono flex align-center"
|
class="px-4 text-[11px] font-mono flex align-center"
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -843,6 +843,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "^18.11.18"
|
"@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":
|
"@types/tmp@^0.2.1":
|
||||||
version "0.2.3"
|
version "0.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165"
|
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"
|
lower-case "^1.1.1"
|
||||||
upper-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:
|
tailwindcss@^3, tailwindcss@^3.3.3:
|
||||||
version "3.3.3"
|
version "3.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
|
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user