mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
Winston log provider
This commit is contained in:
parent
a11e819d5e
commit
46aeaaa1c2
@ -34,7 +34,7 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
name: `health-compact`,
|
||||
restart_delay: 60 * 1000, // 1 minute
|
||||
restart_delay: 60 * 60 * 1000 * 24, // 1 day
|
||||
script: 'pnpm prod:cli health compact',
|
||||
},
|
||||
],
|
||||
|
@ -65,7 +65,8 @@
|
||||
"url-pattern": "^1.0.3",
|
||||
"vhost": "^3.0.2",
|
||||
"winston": "^3.11.0",
|
||||
"winston-syslog": "^2.7.0"
|
||||
"winston-syslog": "^2.7.0",
|
||||
"winston-transport": "^4.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
|
@ -140,7 +140,6 @@ export class PhFs implements FileSystem {
|
||||
? normalize(resolvedVirtualPath)
|
||||
: join('/', this.cwd, resolvedVirtualPath)
|
||||
|
||||
console.log(`***finalVirtualPath`, { finalVirtualPath })
|
||||
// Create local filesystem path using the platform separator
|
||||
const [empty, subdomain, ...restOfVirtualPath] = finalVirtualPath.split('/')
|
||||
dbg({
|
||||
@ -154,7 +153,7 @@ export class PhFs implements FileSystem {
|
||||
|
||||
// Check if the instance is valid
|
||||
const instance = await (async () => {
|
||||
console.log(`***checking validity`, { subdomain })
|
||||
dbg(`checking validity`, { subdomain })
|
||||
if (!subdomain) return
|
||||
const instance = await this.client
|
||||
.collection(`instances`)
|
||||
@ -225,8 +224,7 @@ export class PhFs implements FileSystem {
|
||||
async list(path = '.') {
|
||||
const { dbg, error } = this.log
|
||||
.create(`list`)
|
||||
.breadcrumb(`cwd:${this.cwd}`)
|
||||
.breadcrumb(path)
|
||||
.breadcrumb({ cwd: this.cwd, path })
|
||||
|
||||
const { fsPath, instance } = await this._resolvePath(path)
|
||||
|
||||
@ -277,9 +275,7 @@ export class PhFs implements FileSystem {
|
||||
|
||||
const { dbg, error } = this.log
|
||||
.create(`get`)
|
||||
.breadcrumb(`cwd:${this.cwd}`)
|
||||
.breadcrumb(fileName)
|
||||
.breadcrumb(fsPath)
|
||||
.breadcrumb({ cwd: this.cwd, fileName, fsPath })
|
||||
dbg(`get`)
|
||||
|
||||
/*
|
||||
@ -325,8 +321,7 @@ export class PhFs implements FileSystem {
|
||||
) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`write`)
|
||||
.breadcrumb(`cwd:${this.cwd}`)
|
||||
.breadcrumb(fileName)
|
||||
.breadcrumb({ cwd: this.cwd, fileName })
|
||||
dbg(`write`)
|
||||
|
||||
const { fsPath, clientPath, instance } = await this._resolvePath(fileName)
|
||||
@ -362,8 +357,7 @@ export class PhFs implements FileSystem {
|
||||
): Promise<any> {
|
||||
const { dbg, error } = this.log
|
||||
.create(`read`)
|
||||
.breadcrumb(`cwd:${this.cwd}`)
|
||||
.breadcrumb(fileName)
|
||||
.breadcrumb({ cwd: this.cwd, fileName })
|
||||
dbg(`read`)
|
||||
|
||||
const { fsPath, clientPath } = await this._resolvePath(fileName)
|
||||
@ -387,8 +381,7 @@ export class PhFs implements FileSystem {
|
||||
async delete(path: string) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`delete`)
|
||||
.breadcrumb(`cwd:${this.cwd}`)
|
||||
.breadcrumb(path)
|
||||
.breadcrumb({ cwd: this.cwd, path })
|
||||
dbg(`delete`)
|
||||
|
||||
const { fsPath, instance } = await this._resolvePath(path)
|
||||
@ -405,8 +398,7 @@ export class PhFs implements FileSystem {
|
||||
async mkdir(path: string) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`mkdir`)
|
||||
.breadcrumb(`cwd:${this.cwd}`)
|
||||
.breadcrumb(path)
|
||||
.breadcrumb({ cwd: this.cwd, path })
|
||||
dbg(`mkdir`)
|
||||
|
||||
const { fsPath } = await this._resolvePath(path)
|
||||
@ -417,9 +409,7 @@ export class PhFs implements FileSystem {
|
||||
async rename(from: string, to: string) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`rename`)
|
||||
.breadcrumb(`cwd:${this.cwd}`)
|
||||
.breadcrumb(from)
|
||||
.breadcrumb(to)
|
||||
.breadcrumb({ cwd: this.cwd, from, to })
|
||||
dbg(`rename`)
|
||||
|
||||
const { fsPath: fromPath, instance } = await this._resolvePath(from)
|
||||
@ -434,9 +424,7 @@ export class PhFs implements FileSystem {
|
||||
async chmod(path: string, mode: Mode) {
|
||||
const { dbg, error } = this.log
|
||||
.create(`chmod`)
|
||||
.breadcrumb(`cwd:${this.cwd}`)
|
||||
.breadcrumb(path)
|
||||
.breadcrumb(mode.toString())
|
||||
.breadcrumb({ cwd: this.cwd, path, mode })
|
||||
dbg(`chmod`)
|
||||
|
||||
const { fsPath } = await this._resolvePath(path)
|
||||
|
@ -1,20 +1,22 @@
|
||||
import { Request } from 'express'
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware'
|
||||
import vhost from 'vhost'
|
||||
import { logger } from '../../../../../core/ioc'
|
||||
|
||||
export function createVhostProxyMiddleware(
|
||||
host: string,
|
||||
target: string,
|
||||
ws = false,
|
||||
) {
|
||||
console.log(`Creating ${host}->${target}`)
|
||||
const { dbg } = logger()
|
||||
dbg(`Creating ${host}->${target}`)
|
||||
const handler = createProxyMiddleware({ target, ws, changeOrigin: ws })
|
||||
return vhost(host, (_req, res, next) => {
|
||||
const req = _req as unknown as Request
|
||||
const method = req.method
|
||||
const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl
|
||||
|
||||
console.log(`${method} ${fullUrl} -> ${target}`)
|
||||
dbg(`${method} ${fullUrl} -> ${target}`)
|
||||
// @ts-ignore
|
||||
return handler(req, res, next)
|
||||
})
|
||||
|
@ -7,7 +7,6 @@ import fs from 'fs'
|
||||
import http from 'http'
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware'
|
||||
import https from 'https'
|
||||
import { LoggerService } from '../../../../../common'
|
||||
import {
|
||||
APEX_DOMAIN,
|
||||
APP_NAME,
|
||||
@ -18,13 +17,13 @@ import {
|
||||
MOTHERSHIP_PORT,
|
||||
SSL_CERT,
|
||||
SSL_KEY,
|
||||
discordAlert,
|
||||
} from '../../../../../core'
|
||||
import { logger } from '../../../../../core/ioc'
|
||||
import { createIpWhitelistMiddleware } from './cidr'
|
||||
import { createVhostProxyMiddleware } from './createVhostProxyMiddleware'
|
||||
|
||||
export const firewall = async () => {
|
||||
const { debug } = LoggerService().create(`proxy`)
|
||||
const { dbg, error } = logger()
|
||||
|
||||
const PROD_ROUTES = {
|
||||
[`${MOTHERSHIP_NAME()}.${APEX_DOMAIN()}`]: `http://localhost:${MOTHERSHIP_PORT()}`,
|
||||
@ -49,10 +48,12 @@ export const firewall = async () => {
|
||||
app.use(createIpWhitelistMiddleware(IPCIDR_LIST()))
|
||||
|
||||
forEach(hostnameRoutes, (target, host) => {
|
||||
dbg(`Creating ${host}->${target}`)
|
||||
app.use(createVhostProxyMiddleware(host, target, IS_DEV()))
|
||||
})
|
||||
|
||||
app.get(`/_api/health`, (req, res, next) => {
|
||||
dbg(`Health check`)
|
||||
res.json({ status: 'ok' })
|
||||
res.end()
|
||||
})
|
||||
@ -65,19 +66,19 @@ export const firewall = async () => {
|
||||
const method = req.method
|
||||
const fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl
|
||||
|
||||
debug(`${method} ${fullUrl} -> ${`http://localhost:${DAEMON_PORT()}`}`)
|
||||
dbg(`${method} ${fullUrl} -> ${`http://localhost:${DAEMON_PORT()}`}`)
|
||||
|
||||
handler(req, res, next)
|
||||
})
|
||||
|
||||
const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
|
||||
discordAlert(err.toString())
|
||||
error(err)
|
||||
res.status(500).send(err.toString())
|
||||
}
|
||||
app.use(errorHandler)
|
||||
|
||||
http.createServer(app).listen(80, () => {
|
||||
console.log('SSL redirect server listening on 80')
|
||||
dbg('SSL redirect server listening on 80')
|
||||
})
|
||||
|
||||
// HTTPS server options
|
||||
@ -88,6 +89,6 @@ export const firewall = async () => {
|
||||
|
||||
// Create HTTPS server
|
||||
https.createServer(httpsOptions, app).listen(443, () => {
|
||||
console.log('HTTPS server running on port 443')
|
||||
dbg('HTTPS server running on port 443')
|
||||
})
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
import { exec } from 'child_process'
|
||||
import { globSync } from 'glob'
|
||||
import { DATA_ROOT } from '../../../../core'
|
||||
import { logger } from '../../../core/ioc'
|
||||
|
||||
export const compact = async () => {
|
||||
const { info } = logger()
|
||||
|
||||
const files = [`data`, `logs`].flatMap((db) =>
|
||||
globSync(`${DATA_ROOT()}/*/pb_data/${db}.db{-shm,-wal}`),
|
||||
)
|
||||
|
||||
files.map(async (file) => {
|
||||
console.log(`Compacting ${file}`)
|
||||
info(`Compacting ${file}`)
|
||||
exec(`sqlite3 ${file} ".tables"`)
|
||||
})
|
||||
console.log(`Compaction complete`)
|
||||
info(`Compaction complete`)
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ export const SqliteService = mkSingleton((config: SqliteServiceConfig) => {
|
||||
filename: string,
|
||||
): Promise<SqliteServiceApi> => {
|
||||
const _dbLogger = LoggerService().create(`SqliteService`)
|
||||
_dbLogger.breadcrumb(filename)
|
||||
_dbLogger.breadcrumb({ filename })
|
||||
const { dbg, error, abort } = _dbLogger
|
||||
|
||||
trace(`Fetching database`, connections)
|
||||
|
@ -3,16 +3,12 @@
|
||||
import { program } from 'commander'
|
||||
import EventSource from 'eventsource'
|
||||
import {
|
||||
DEBUG,
|
||||
DefaultSettingsService,
|
||||
LogLevelName,
|
||||
LoggerService,
|
||||
PH_PLUGINS,
|
||||
SETTINGS,
|
||||
loadPlugins,
|
||||
} from '../../core'
|
||||
import { version } from '../../package.json'
|
||||
import { GobotService } from '../services/GobotService'
|
||||
import { EdgeCommand } from './commands/EdgeCommand'
|
||||
import { FirewallCommand } from './commands/FirewallCommand'
|
||||
import { HealthCommand } from './commands/HealthCommand'
|
||||
@ -20,17 +16,13 @@ import { MothershipCommand } from './commands/MothershipCommand'
|
||||
import { PocketBaseCommand } from './commands/PocketBaseCommand'
|
||||
import { SendMailCommand } from './commands/SendMailCommand'
|
||||
import { ServeCommand } from './commands/ServeCommand'
|
||||
import './ioc'
|
||||
|
||||
export type GlobalOptions = {
|
||||
logLevel?: LogLevelName
|
||||
debug: boolean
|
||||
}
|
||||
|
||||
DefaultSettingsService(SETTINGS)
|
||||
|
||||
LoggerService({ level: DEBUG() ? LogLevelName.Debug : LogLevelName.Info })
|
||||
GobotService({})
|
||||
|
||||
//@ts-ignore
|
||||
global.EventSource = EventSource
|
||||
|
||||
|
11
packages/pockethost/src/cli/ioc.ts
Normal file
11
packages/pockethost/src/cli/ioc.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { LoggerService } from '../common'
|
||||
import { RegisterEnvSettingsService } from '../constants'
|
||||
import { ioc } from '../core/ioc'
|
||||
import { WinstonLoggerService } from '../core/winston'
|
||||
import { GobotService } from '../services/GobotService'
|
||||
|
||||
ioc.register('logger', WinstonLoggerService({}))
|
||||
|
||||
RegisterEnvSettingsService()
|
||||
LoggerService({})
|
||||
GobotService({})
|
@ -1,16 +1,13 @@
|
||||
/// <require "node">
|
||||
|
||||
import chalk from 'chalk'
|
||||
import stringify from 'json-stringify-safe'
|
||||
import { action, mergeConfig, mkSingleton } from '.'
|
||||
import { mkSingleton } from '.'
|
||||
import { logger } from '../core/ioc'
|
||||
|
||||
export type LoggerConfig = {
|
||||
level: LogLevelName
|
||||
pfx: string[]
|
||||
}
|
||||
|
||||
export type Logger = ReturnType<typeof createLogger>
|
||||
|
||||
export const isLevelLte = (a: LogLevelName, b: LogLevelName) => {
|
||||
return LogLevels[a] <= LogLevels[b]
|
||||
}
|
||||
@ -53,118 +50,23 @@ export const LogLevels = {
|
||||
[LogLevelName.Abort]: 6,
|
||||
} as const
|
||||
|
||||
export const createLogger = (config: Partial<LoggerConfig>) => {
|
||||
const _config = mergeConfig<LoggerConfig>(
|
||||
{
|
||||
level: LogLevelName.Debug,
|
||||
pfx: [''],
|
||||
},
|
||||
config,
|
||||
)
|
||||
const { pfx } = _config
|
||||
|
||||
const setLevel = (level: LogLevelName) => {
|
||||
_config.level = level
|
||||
}
|
||||
|
||||
const _pfx = (s: string) =>
|
||||
[new Date().toISOString(), s, ...pfx]
|
||||
.filter((v) => !!v)
|
||||
.map((p) => `[${p}]`)
|
||||
.join(' ')
|
||||
|
||||
const _log = (levelIn: LogLevelName, ...args: any[]) => {
|
||||
action(`log`, _config.level, levelIn, args)
|
||||
}
|
||||
|
||||
const raw = (...args: any[]) => {
|
||||
_log(LogLevelName.Raw, _pfx('RAW'), ...args)
|
||||
}
|
||||
|
||||
const trace = (...args: any[]) => {
|
||||
_log(LogLevelName.Trace, _pfx(`TRACE`), ...args)
|
||||
}
|
||||
|
||||
const dbg = (...args: any[]) => {
|
||||
_log(LogLevelName.Debug, _pfx(chalk.blueBright('DBG')), ...args)
|
||||
}
|
||||
|
||||
const info = (...args: any[]) => {
|
||||
_log(
|
||||
LogLevelName.Info,
|
||||
_pfx(
|
||||
isLevelGt(LogLevelName.Info, _config.level) ? chalk.gray(`INFO`) : '',
|
||||
),
|
||||
...args,
|
||||
)
|
||||
}
|
||||
|
||||
const warn = (...args: any[]) => {
|
||||
_log(
|
||||
LogLevelName.Warn,
|
||||
_pfx(chalk.yellow(chalk.cyanBright('WARN'))),
|
||||
...args,
|
||||
)
|
||||
}
|
||||
|
||||
const error = (...args: any[]) => {
|
||||
_log(LogLevelName.Error, ...[_pfx(chalk.bgRed(`ERROR`)), ...args])
|
||||
}
|
||||
|
||||
const criticalError = (...args: any[]) => {
|
||||
_log(LogLevelName.Error, ...[_pfx(chalk.bgRed(`ERROR`)), ...args])
|
||||
new Error().stack?.split(/\n/).forEach((line) => {
|
||||
_log(LogLevelName.Debug, _pfx(chalk.bgRed(`ERROR`)), line)
|
||||
})
|
||||
}
|
||||
|
||||
const abort = (...args: any[]): never => {
|
||||
_log(LogLevelName.Abort, true, ...[_pfx(chalk.bgRed(`ABORT`)), ...args])
|
||||
throw new Error(`Fatal error: ${stringify(args)}`)
|
||||
}
|
||||
|
||||
const create = (name: string, configOverride?: Partial<LoggerConfig>) =>
|
||||
createLogger({
|
||||
..._config,
|
||||
...configOverride,
|
||||
pfx: [..._config.pfx, name],
|
||||
})
|
||||
|
||||
const breadcrumb = (s: string | object) => {
|
||||
if (typeof s === 'string') {
|
||||
pfx.push(s)
|
||||
} else {
|
||||
Object.entries(s).forEach(([k, v]) => pfx.push(`${k}: ${v}`))
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
// Compatibility func
|
||||
const child = (name: string) => create(name)
|
||||
|
||||
const api = {
|
||||
raw,
|
||||
dbg,
|
||||
warn,
|
||||
info,
|
||||
error,
|
||||
criticalError,
|
||||
create,
|
||||
child,
|
||||
trace,
|
||||
debug: dbg,
|
||||
breadcrumb,
|
||||
abort,
|
||||
shutdown() {
|
||||
dbg(`Logger shutting down`)
|
||||
},
|
||||
setLevel,
|
||||
}
|
||||
return api
|
||||
export type Logger = {
|
||||
raw: (...args: any[]) => void
|
||||
dbg: (...args: any[]) => void
|
||||
warn: (...args: any[]) => void
|
||||
info: (...args: any[]) => void
|
||||
error: (...args: any[]) => void
|
||||
criticalError: (...args: any[]) => void
|
||||
create: (name: string, configOverride?: Partial<LoggerConfig>) => Logger
|
||||
child: (name: string) => Logger
|
||||
trace: (...args: any[]) => void
|
||||
debug: (...args: any[]) => void
|
||||
breadcrumb: (s: object) => Logger
|
||||
abort: (...args: any[]) => never
|
||||
shutdown: () => void
|
||||
setLevel: (level: LogLevelName) => void
|
||||
}
|
||||
|
||||
export type LoggerServiceApi = ReturnType<typeof createLogger>
|
||||
|
||||
export const LoggerService = mkSingleton((config: Partial<LoggerConfig> = {}) =>
|
||||
createLogger(config),
|
||||
logger(),
|
||||
)
|
||||
|
@ -6,15 +6,11 @@ import { default as env } from 'env-var'
|
||||
import { mkdirSync, writeFileSync } from 'fs'
|
||||
import { dirname, join } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { LogEntry } from 'winston'
|
||||
import {
|
||||
InstanceFields,
|
||||
InstanceId,
|
||||
IoCManager,
|
||||
SettingsHandlerFactory,
|
||||
SettingsService,
|
||||
UserFields,
|
||||
mkSingleton,
|
||||
} from '../core'
|
||||
import {
|
||||
mkBoolean,
|
||||
@ -23,6 +19,7 @@ import {
|
||||
mkPath,
|
||||
mkString,
|
||||
} from './core/Settings'
|
||||
import { ioc, settings } from './core/ioc'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
@ -147,55 +144,23 @@ export const SETTINGS = {
|
||||
PH_GOBOT_ROOT: mkPath(join(_PH_HOME, 'gobot'), { create: true }),
|
||||
}
|
||||
|
||||
export type Settings = ReturnType<typeof DefaultSettingsService>
|
||||
export type Settings = ReturnType<typeof RegisterEnvSettingsService>
|
||||
export type SettingsDefinition = {
|
||||
[_ in keyof Settings]: SettingsHandlerFactory<Settings[_]>
|
||||
}
|
||||
|
||||
export const DefaultSettingsService = mkSingleton(
|
||||
(settings: typeof SETTINGS) => {
|
||||
const _settings = SettingsService(settings)
|
||||
export const RegisterEnvSettingsService = () => {
|
||||
const _settings = SettingsService(SETTINGS)
|
||||
|
||||
ioc.register('settings', _settings)
|
||||
ioc.register('settings', _settings)
|
||||
|
||||
if (DEBUG()) {
|
||||
logConstants()
|
||||
}
|
||||
if (DEBUG()) {
|
||||
logConstants()
|
||||
}
|
||||
|
||||
return _settings
|
||||
},
|
||||
)
|
||||
|
||||
export type MothershipProvider = {
|
||||
getAllInstances(): Promise<InstanceFields[]>
|
||||
getInstanceById(id: InstanceId): Promise<[InstanceFields, UserFields] | []>
|
||||
getInstanceBySubdomain(
|
||||
subdomain: InstanceFields['subdomain'],
|
||||
): Promise<[InstanceFields, UserFields] | []>
|
||||
updateInstance(id: InstanceId, fields: Partial<InstanceFields>): Promise<void>
|
||||
return _settings
|
||||
}
|
||||
|
||||
type UnsubFunc = () => void
|
||||
|
||||
export type InstanceLogProvider = (
|
||||
instanceId: InstanceId,
|
||||
target: string,
|
||||
) => {
|
||||
info(msg: string): void
|
||||
error(msg: string): void
|
||||
tail(linesBack: number, data: (line: LogEntry) => void): UnsubFunc
|
||||
}
|
||||
|
||||
export const ioc = new IoCManager<{
|
||||
settings: Settings
|
||||
mothership: MothershipProvider
|
||||
instanceLogger: InstanceLogProvider
|
||||
}>()
|
||||
|
||||
export const settings = () => ioc.service('settings')
|
||||
export const mothership = () => ioc.service('mothership')
|
||||
export const instanceLogger = () => ioc.service('instanceLogger')
|
||||
|
||||
/** Accessors */
|
||||
export const PH_PLUGINS = () => settings().PH_PLUGINS
|
||||
|
||||
|
19
packages/pockethost/src/core/DiscordTransport.ts
Normal file
19
packages/pockethost/src/core/DiscordTransport.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import TransportStream from 'winston-transport'
|
||||
import { discordAlert } from '..'
|
||||
|
||||
export type DiscordTransportType = {
|
||||
webhookUrl: string
|
||||
} & TransportStream.TransportStreamOptions
|
||||
|
||||
export class DiscordTransport extends TransportStream {
|
||||
private url: string
|
||||
constructor(opts: DiscordTransportType) {
|
||||
super(opts)
|
||||
this.url = opts.webhookUrl
|
||||
}
|
||||
|
||||
log(info: any, callback: any) {
|
||||
discordAlert(info)
|
||||
callback()
|
||||
}
|
||||
}
|
@ -20,8 +20,7 @@ export function SyslogLogger(instanceId: string, target: string) {
|
||||
|
||||
const { error, warn } = LoggerService()
|
||||
.create('SyslogLogger')
|
||||
.breadcrumb(instanceId)
|
||||
.breadcrumb(target)
|
||||
.breadcrumb({ instanceId, target })
|
||||
|
||||
const api = {
|
||||
info: (msg: string) => {
|
||||
|
35
packages/pockethost/src/core/ioc.ts
Normal file
35
packages/pockethost/src/core/ioc.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { LogEntry } from 'winston'
|
||||
import { InstanceFields, InstanceId, Logger, UserFields } from '../common'
|
||||
import { IoCManager } from '../common/ioc'
|
||||
import { Settings } from '../constants'
|
||||
|
||||
export type MothershipProvider = {
|
||||
getAllInstances(): Promise<InstanceFields[]>
|
||||
getInstanceById(id: InstanceId): Promise<[InstanceFields, UserFields] | []>
|
||||
getInstanceBySubdomain(
|
||||
subdomain: InstanceFields['subdomain'],
|
||||
): Promise<[InstanceFields, UserFields] | []>
|
||||
updateInstance(id: InstanceId, fields: Partial<InstanceFields>): Promise<void>
|
||||
}
|
||||
type UnsubFunc = () => void
|
||||
|
||||
export type InstanceLogProvider = (
|
||||
instanceId: InstanceId,
|
||||
target: string,
|
||||
) => {
|
||||
info(msg: string): void
|
||||
error(msg: string): void
|
||||
tail(linesBack: number, data: (line: LogEntry) => void): UnsubFunc
|
||||
}
|
||||
|
||||
export const ioc = new IoCManager<{
|
||||
settings: Settings
|
||||
mothership: MothershipProvider
|
||||
instanceLogger: InstanceLogProvider
|
||||
logger: Logger
|
||||
}>()
|
||||
|
||||
export const settings = () => ioc.service('settings')
|
||||
export const mothership = () => ioc.service('mothership')
|
||||
export const instanceLogger = () => ioc.service('instanceLogger')
|
||||
export const logger = () => ioc.service('logger')
|
@ -33,7 +33,7 @@ export const tryFetch = async (
|
||||
timeoutMs: TRYFETCH_TIMEOUT_MS,
|
||||
...config,
|
||||
}
|
||||
const logger = LoggerService().create(`tryFetch`).breadcrumb(url)
|
||||
const logger = LoggerService().create(`tryFetch`).breadcrumb({ url })
|
||||
const { dbg } = logger
|
||||
return new Promise<Response>((resolve, reject) => {
|
||||
const again = () => setTimeout(_real_tryFetch, retryMs)
|
||||
|
111
packages/pockethost/src/core/winston.ts
Normal file
111
packages/pockethost/src/core/winston.ts
Normal file
@ -0,0 +1,111 @@
|
||||
import { inspect } from 'node:util'
|
||||
import winston from 'winston'
|
||||
import { Logger, mkSingleton } from '../common'
|
||||
import { DEBUG, DISCORD_ALERT_CHANNEL_URL } from '../constants'
|
||||
import { DiscordTransport } from './DiscordTransport'
|
||||
|
||||
const format = winston.format.combine(
|
||||
winston.format.colorize(),
|
||||
winston.format.printf(({ level, message, timestamp, ...meta }) => {
|
||||
const final: string[] = []
|
||||
message.forEach((m: string) => {
|
||||
if (typeof m === 'string' && !!m.match(/\n/)) {
|
||||
final.push(...m.split(/\n/))
|
||||
} else if (typeof m === 'object') {
|
||||
final.push(inspect(m, { depth: null }))
|
||||
} else {
|
||||
final.push(m)
|
||||
}
|
||||
})
|
||||
return `${level}: ${final.join(' ')}`
|
||||
}),
|
||||
)
|
||||
|
||||
export const WinstonLoggerService = mkSingleton<{}, Logger>(() => {
|
||||
const logger = winston.createLogger({
|
||||
format: winston.format.json(),
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
level: DEBUG() ? 'debug' : 'info',
|
||||
format,
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'error.log',
|
||||
level: 'error',
|
||||
maxsize: 100 * 1024 * 1024,
|
||||
maxFiles: 10,
|
||||
tailable: true,
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'debug.log',
|
||||
level: 'debug',
|
||||
maxsize: 100 * 1024 * 1024,
|
||||
maxFiles: 10,
|
||||
tailable: true,
|
||||
}),
|
||||
],
|
||||
rejectionHandlers: [
|
||||
new winston.transports.Console({
|
||||
level: 'error',
|
||||
format,
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'rejections.log',
|
||||
maxsize: 100 * 1024 * 1024,
|
||||
maxFiles: 10,
|
||||
tailable: true,
|
||||
}),
|
||||
],
|
||||
exceptionHandlers: [
|
||||
new winston.transports.Console({
|
||||
level: 'error',
|
||||
format,
|
||||
}),
|
||||
new winston.transports.File({
|
||||
filename: 'exceptions.log',
|
||||
maxsize: 100 * 1024 * 1024,
|
||||
maxFiles: 10,
|
||||
tailable: true,
|
||||
}),
|
||||
],
|
||||
defaultMeta: {},
|
||||
})
|
||||
logger.exitOnError = true
|
||||
|
||||
{
|
||||
const url = DISCORD_ALERT_CHANNEL_URL()
|
||||
if (url) {
|
||||
logger.add(new DiscordTransport({ level: 'error', webhookUrl: url }))
|
||||
}
|
||||
}
|
||||
|
||||
const createApi = (logger: winston.Logger): Logger => {
|
||||
const api: Logger = {
|
||||
create: (name: string) => {
|
||||
return createApi(logger.child({ ...logger.defaultMeta, name }))
|
||||
},
|
||||
raw: (...args: any[]) => logger.silly(args),
|
||||
dbg: (...args: any[]) => logger.debug(args),
|
||||
warn: (...args: any[]) => logger.warn(args),
|
||||
info: (...args: any[]) => logger.info(args),
|
||||
error: (...args: any[]) => logger.error(args),
|
||||
criticalError: (...args: any[]) => logger.error(args),
|
||||
setLevel: (level) => {},
|
||||
trace: (...args: any[]) => logger.silly(args),
|
||||
debug: (...args: any[]) => logger.debug(args),
|
||||
breadcrumb: (s) => {
|
||||
Object.assign(logger.defaultMeta, s)
|
||||
return api
|
||||
},
|
||||
shutdown: () => {},
|
||||
child: (name) => createApi(logger.child({ name })),
|
||||
abort: (...args) => {
|
||||
logger.error(args)
|
||||
process.exit(1)
|
||||
},
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
return createApi(logger)
|
||||
})
|
@ -33,7 +33,9 @@ export function InstanceLogger(
|
||||
target: string,
|
||||
options: Partial<InstanceLoggerOptions> = {},
|
||||
) {
|
||||
const { dbg, info } = LoggerService().create(instanceId).breadcrumb(target)
|
||||
const { dbg, info } = LoggerService()
|
||||
.create(instanceId)
|
||||
.breadcrumb({ target })
|
||||
const { ttl } = mergeConfig<InstanceLoggerOptions>({ ttl: 0 }, options)
|
||||
|
||||
dbg({ ttl })
|
||||
@ -85,8 +87,7 @@ export function InstanceLogger(
|
||||
|
||||
const { error, warn } = LoggerService()
|
||||
.create('InstanceLogger')
|
||||
.breadcrumb(instanceId)
|
||||
.breadcrumb(target)
|
||||
.breadcrumb({ instanceId, target })
|
||||
|
||||
const resetTtl = (() => {
|
||||
let tid: ReturnType<typeof setTimeout>
|
||||
|
@ -72,7 +72,7 @@ export const instanceService = mkSingleton(
|
||||
const getInstanceApi = (instance: InstanceFields): Promise<InstanceApi> => {
|
||||
const _logger = instanceServiceLogger.create(`getInstanceApi`)
|
||||
const { id, subdomain, version } = instance
|
||||
_logger.breadcrumb(`${subdomain}:${id}:${version}`)
|
||||
_logger.breadcrumb({ subdomain, id, version })
|
||||
const { dbg, trace } = _logger
|
||||
return new Promise<InstanceApi>((resolve, reject) => {
|
||||
let maxTries = instanceApiTimeoutMs / instanceApiCheckIntervalMs
|
||||
@ -256,7 +256,7 @@ export const instanceService = mkSingleton(
|
||||
dbg(`shut down: releasing port`)
|
||||
releasePort()
|
||||
}, CLEANUP_PRIORITY_LAST)
|
||||
systemInstanceLogger.breadcrumb(`port:${newPort}`)
|
||||
systemInstanceLogger.breadcrumb({ port: newPort })
|
||||
dbg(`Found port`)
|
||||
|
||||
/*
|
||||
|
@ -94,7 +94,7 @@ export const createPocketbaseService = async (
|
||||
dev,
|
||||
} = _cfg
|
||||
|
||||
logger.breadcrumb(subdomain).breadcrumb(instanceId)
|
||||
logger.breadcrumb({ subdomain, instanceId })
|
||||
const iLogger = SyslogLogger(instanceId, 'exec')
|
||||
cm.add(async () => {
|
||||
dbg(`Shutting down iLogger`)
|
||||
@ -259,7 +259,7 @@ export const createPocketbaseService = async (
|
||||
cm.shutdown().catch(error)
|
||||
})
|
||||
const url = mkInternalUrl(port)
|
||||
logger.breadcrumb(url)
|
||||
logger.breadcrumb({ url })
|
||||
dbg(`Making exit hook for ${url}`)
|
||||
const unsub = asyncExitHook(async () => {
|
||||
await api.kill()
|
||||
|
28
pnpm-lock.yaml
generated
28
pnpm-lock.yaml
generated
@ -479,6 +479,9 @@ importers:
|
||||
winston-syslog:
|
||||
specifier: ^2.7.0
|
||||
version: 2.7.0(winston@3.11.0)
|
||||
winston-transport:
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1
|
||||
devDependencies:
|
||||
'@types/cors':
|
||||
specifier: ^2.8.17
|
||||
@ -3894,6 +3897,10 @@ packages:
|
||||
resolution: {integrity: sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
logform@2.6.1:
|
||||
resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lower-case@2.0.2:
|
||||
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
|
||||
|
||||
@ -6012,8 +6019,8 @@ packages:
|
||||
peerDependencies:
|
||||
winston: ^3.8.2
|
||||
|
||||
winston-transport@4.6.0:
|
||||
resolution: {integrity: sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==}
|
||||
winston-transport@4.7.1:
|
||||
resolution: {integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
winston@3.11.0:
|
||||
@ -7159,7 +7166,7 @@ snapshots:
|
||||
'@types/glossy': 0.1.3
|
||||
'@types/node': 20.8.10
|
||||
winston: 3.11.0
|
||||
winston-transport: 4.6.0
|
||||
winston-transport: 4.7.1
|
||||
|
||||
JSONStream@1.3.5:
|
||||
dependencies:
|
||||
@ -9832,6 +9839,15 @@ snapshots:
|
||||
safe-stable-stringify: 2.4.3
|
||||
triple-beam: 1.4.1
|
||||
|
||||
logform@2.6.1:
|
||||
dependencies:
|
||||
'@colors/colors': 1.6.0
|
||||
'@types/triple-beam': 1.3.4
|
||||
fecha: 4.2.3
|
||||
ms: 2.1.3
|
||||
safe-stable-stringify: 2.4.3
|
||||
triple-beam: 1.4.1
|
||||
|
||||
lower-case@2.0.2:
|
||||
dependencies:
|
||||
tslib: 2.6.2
|
||||
@ -12167,9 +12183,9 @@ snapshots:
|
||||
optionalDependencies:
|
||||
unix-dgram: 2.0.6
|
||||
|
||||
winston-transport@4.6.0:
|
||||
winston-transport@4.7.1:
|
||||
dependencies:
|
||||
logform: 2.6.0
|
||||
logform: 2.6.1
|
||||
readable-stream: 3.6.2
|
||||
triple-beam: 1.4.1
|
||||
|
||||
@ -12185,7 +12201,7 @@ snapshots:
|
||||
safe-stable-stringify: 2.4.3
|
||||
stack-trace: 0.0.10
|
||||
triple-beam: 1.4.1
|
||||
winston-transport: 4.6.0
|
||||
winston-transport: 4.7.1
|
||||
|
||||
with@7.0.2:
|
||||
dependencies:
|
||||
|
Loading…
x
Reference in New Issue
Block a user