feat: syslogd

This commit is contained in:
Ben Allfree 2024-01-18 22:34:15 +00:00
parent 7b9cbc3352
commit 34fe0fe43e
9 changed files with 200 additions and 19 deletions

View File

@ -20,6 +20,7 @@
"POCKETSTREAM", "POCKETSTREAM",
"rizzdown", "rizzdown",
"superadmin", "superadmin",
"syslogd",
"unzipper", "unzipper",
"upserting" "upserting"
] ]

View File

@ -12,6 +12,10 @@ module.exports = {
name: `edge-ftp`, name: `edge-ftp`,
script: 'pnpm prod:edge:ftp', script: 'pnpm prod:edge:ftp',
}, },
{
name: `edge-syslog`,
script: 'pnpm prod:edge:syslog',
},
{ {
name: `mothership`, name: `mothership`,
script: 'pnpm prod:mothership', script: 'pnpm prod:mothership',

View File

@ -24,12 +24,14 @@
"dev:superadmin": "cd frontends/superadmin && pnpm dev", "dev:superadmin": "cd frontends/superadmin && pnpm dev",
"dev:edge:daemon": "tsx watch src/cli/edge-daemon.ts", "dev:edge:daemon": "tsx watch src/cli/edge-daemon.ts",
"dev:edge:ftp": "tsx watch src/cli/edge-ftp.ts", "dev:edge:ftp": "tsx watch src/cli/edge-ftp.ts",
"dev:edge:syslogd": "tsx watch src/cli/edge-syslogd.ts",
"dev:downloader": "pnpm download-versions", "dev:downloader": "pnpm download-versions",
"dev:mothership:maildev": "npx -y maildev", "dev:mothership:maildev": "npx -y maildev",
"dev:mothership:pocketbase": "nodemon --signal SIGTERM --watch src --exec tsx ./src/cli/mothership.ts", "dev:mothership:pocketbase": "nodemon --signal SIGTERM --watch src --exec tsx ./src/cli/mothership.ts",
"prod:proxy": "dotenv tsx ./src/cli/proxy/server.ts", "prod:proxy": "dotenv tsx ./src/cli/proxy/server.ts",
"prod:edge:daemon": "tsx src/cli/edge-daemon.ts", "prod:edge:daemon": "tsx src/cli/edge-daemon.ts",
"prod:edge:ftp": "tsx src/cli/edge-ftp.ts", "prod:edge:ftp": "tsx src/cli/edge-ftp.ts",
"prod:edge:syslog": "tsx src/cli/edge-syslogd.ts",
"prod:mothership": "tsx src/cli/mothership.ts", "prod:mothership": "tsx src/cli/mothership.ts",
"plop": "plop", "plop": "plop",
"nofile": "cat /proc/sys/fs/file-nr", "nofile": "cat /proc/sys/fs/file-nr",
@ -50,6 +52,7 @@
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@s-libs/micro-dash": "^16.1.0", "@s-libs/micro-dash": "^16.1.0",
"@types/winston-syslog": "^2.4.3",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"boolean": "^3.2.0", "boolean": "^3.2.0",
"bottleneck": "^2.19.5", "bottleneck": "^2.19.5",
@ -77,10 +80,12 @@
"pocketbase": "^0.20.1", "pocketbase": "^0.20.1",
"semver": "^7.5.4", "semver": "^7.5.4",
"sqlite3": "^5.1.6", "sqlite3": "^5.1.6",
"syslog-parse": "^2.0.0",
"tail": "^2.2.6", "tail": "^2.2.6",
"tmp": "^0.2.1", "tmp": "^0.2.1",
"url-pattern": "^1.0.3", "url-pattern": "^1.0.3",
"winston": "^3.11.0" "winston": "^3.11.0",
"winston-syslog": "^2.7.0"
}, },
"devDependencies": { "devDependencies": {
"@swc/cli": "^0.1.62", "@swc/cli": "^0.1.62",

72
pnpm-lock.yaml generated
View File

@ -11,6 +11,9 @@ importers:
'@s-libs/micro-dash': '@s-libs/micro-dash':
specifier: ^16.1.0 specifier: ^16.1.0
version: 16.1.0 version: 16.1.0
'@types/winston-syslog':
specifier: ^2.4.3
version: 2.4.3
ajv: ajv:
specifier: ^8.12.0 specifier: ^8.12.0
version: 8.12.0 version: 8.12.0
@ -92,6 +95,9 @@ importers:
sqlite3: sqlite3:
specifier: ^5.1.6 specifier: ^5.1.6
version: 5.1.6 version: 5.1.6
syslog-parse:
specifier: ^2.0.0
version: 2.0.0
tail: tail:
specifier: ^2.2.6 specifier: ^2.2.6
version: 2.2.6 version: 2.2.6
@ -104,6 +110,9 @@ importers:
winston: winston:
specifier: ^3.11.0 specifier: ^3.11.0
version: 3.11.0 version: 3.11.0
winston-syslog:
specifier: ^2.7.0
version: 2.7.0(winston@3.11.0)
devDependencies: devDependencies:
'@swc/cli': '@swc/cli':
specifier: ^0.1.62 specifier: ^0.1.62
@ -1421,6 +1430,12 @@ packages:
resolution: {integrity: sha512-mZ0onxTS5OyfSwBNecTKT0h79e4XXHrc9RI5tQfEAf+Fp6NbBmNnc0kg59HO+97V+y3opS+sfo4k4qpYwLt6NQ==} resolution: {integrity: sha512-mZ0onxTS5OyfSwBNecTKT0h79e4XXHrc9RI5tQfEAf+Fp6NbBmNnc0kg59HO+97V+y3opS+sfo4k4qpYwLt6NQ==}
dev: true dev: true
/@types/glossy@0.1.3:
resolution: {integrity: sha512-CrdAR+ZgRf0MQnDAW4tUm2LpPmfC6sAWlrBwcX0O2oUKyZvseb6wlHZ0alo++DyaLckxqM4CUa+EfzyITJM7mA==}
dependencies:
'@types/node': 20.8.10
dev: false
/@types/http-cache-semantics@4.0.3: /@types/http-cache-semantics@4.0.3:
resolution: {integrity: sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==} resolution: {integrity: sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==}
dev: true dev: true
@ -1527,7 +1542,6 @@ packages:
resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==} resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==}
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
dev: true
/@types/pug@2.0.8: /@types/pug@2.0.8:
resolution: {integrity: sha512-QzhsZ1dMGyJbn/D9V80zp4GIA4J4rfAjCCxc3MP+new0E8dyVdSkR735Lx+n3LIaHNFcjHL5+TbziccuT+fdoQ==} resolution: {integrity: sha512-QzhsZ1dMGyJbn/D9V80zp4GIA4J4rfAjCCxc3MP+new0E8dyVdSkR735Lx+n3LIaHNFcjHL5+TbziccuT+fdoQ==}
@ -1606,6 +1620,15 @@ packages:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
dev: true dev: true
/@types/winston-syslog@2.4.3:
resolution: {integrity: sha512-z9mO5hxDls4lSTth76sddIETonCMLguppeudk1YxBz4Y/OmdRkeKMfrOTfH74T9gN5WllLnF8XbHdiM8K6EL7A==}
dependencies:
'@types/glossy': 0.1.3
'@types/node': 20.8.10
winston: 3.11.0
winston-transport: 4.6.0
dev: false
/a-sync-waterfall@1.0.1: /a-sync-waterfall@1.0.1:
resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==} resolution: {integrity: sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==}
dev: true dev: true
@ -1961,6 +1984,14 @@ packages:
resolution: {integrity: sha512-v4N2l3RxL+m4zDxyxz3Ne2aTmiPn8ZUpKFpdPtO+ItW1NcTCXA7JeHG5GMBSvoKSkQZ9ycS+EouDVxYB9ufKWA==} resolution: {integrity: sha512-v4N2l3RxL+m4zDxyxz3Ne2aTmiPn8ZUpKFpdPtO+ItW1NcTCXA7JeHG5GMBSvoKSkQZ9ycS+EouDVxYB9ufKWA==}
dev: true dev: true
/bindings@1.5.0:
resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
requiresBuild: true
dependencies:
file-uri-to-path: 1.0.0
dev: false
optional: true
/bl@1.2.3: /bl@1.2.3:
resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==} resolution: {integrity: sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww==}
dependencies: dependencies:
@ -3491,6 +3522,12 @@ packages:
engines: {node: '>=4'} engines: {node: '>=4'}
dev: false dev: false
/file-uri-to-path@1.0.0:
resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
requiresBuild: true
dev: false
optional: true
/filelist@1.0.4: /filelist@1.0.4:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
dependencies: dependencies:
@ -3890,6 +3927,11 @@ packages:
resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==}
dev: true dev: true
/glossy@0.1.7:
resolution: {integrity: sha512-mTCC51QFadK75MvAhrL5nPVIP291NjML1guo10Sa7Yj04tJU4V++Vgm780NIddg9etQD9D8FM67hFGqM8EE2HQ==}
engines: {node: '>= 0.2.5'}
dev: false
/gopd@1.0.1: /gopd@1.0.1:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
dependencies: dependencies:
@ -7322,6 +7364,11 @@ packages:
periscopic: 3.1.0 periscopic: 3.1.0
dev: true dev: true
/syslog-parse@2.0.0:
resolution: {integrity: sha512-FI6xGyKM9dRdNCrCWiEy1QhRZskDYkW+lUNAIXkFeht0/XCsSdZ7UsPANFLk0h8b+8Is6Ll8bllUNjME+XCANA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
dev: false
/tail@2.2.6: /tail@2.2.6:
resolution: {integrity: sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw==} resolution: {integrity: sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw==}
engines: {node: '>= 6.0.0'} engines: {node: '>= 6.0.0'}
@ -7627,7 +7674,6 @@ packages:
/undici-types@5.26.5: /undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
dev: true
/undici@5.26.5: /undici@5.26.5:
resolution: {integrity: sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==} resolution: {integrity: sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==}
@ -7665,6 +7711,16 @@ packages:
'@types/unist': 3.0.2 '@types/unist': 3.0.2
dev: true dev: true
/unix-dgram@2.0.6:
resolution: {integrity: sha512-AURroAsb73BZ6CdAyMrTk/hYKNj3DuYYEuOaB8bYMOHGKupRNScw90Q5C71tWJc3uE7dIeXRyuwN0xLLq3vDTg==}
engines: {node: '>=0.10.48'}
requiresBuild: true
dependencies:
bindings: 1.5.0
nan: 2.18.0
dev: false
optional: true
/unpipe@1.0.0: /unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -7864,6 +7920,18 @@ packages:
string-width: 5.1.2 string-width: 5.1.2
dev: true dev: true
/winston-syslog@2.7.0(winston@3.11.0):
resolution: {integrity: sha512-w+V0lHO2W6XqcYlvVi4DrblwJShvQbAaruRvUlMPzH1Z+dYvUvo4ra2hhoF6UNTFmC9LBltcTG05ypYL6S/B8A==}
engines: {node: '>= 8'}
peerDependencies:
winston: ^3.8.2
dependencies:
glossy: 0.1.7
winston: 3.11.0
optionalDependencies:
unix-dgram: 2.0.6
dev: false
/winston-transport@4.6.0: /winston-transport@4.6.0:
resolution: {integrity: sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==} resolution: {integrity: sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}

53
src/cli/edge-syslogd.ts Normal file
View File

@ -0,0 +1,53 @@
import {
DEBUG,
DefaultSettingsService,
SETTINGS,
SYSLOGD_PORT,
} from '$constants'
import { InstanceLogger } from '$services'
import { LogLevelName, LoggerService } from '$src/shared'
import * as dgram from 'dgram'
import parse from 'syslog-parse'
const server = dgram.createSocket('udp4')
DefaultSettingsService(SETTINGS)
const PORT = SYSLOGD_PORT()
const HOST = '0.0.0.0'
const { dbg, info, error } = LoggerService({
level: DEBUG() ? LogLevelName.Debug : LogLevelName.Info,
}).create(`edge-syslogd`)
console.log(`debug is ${DEBUG()}`)
server.on('error', (err) => {
console.log(`Server error:\n${err.stack}`)
server.close()
})
server.on('message', (msg, rinfo) => {
const raw = msg.toString()
const parsed = parse(raw)
if (!parsed) {
return
}
dbg(parsed)
const { process: instanceId, severity, message } = parsed
const logger = InstanceLogger(instanceId, `exec`)
if (severity === 'info') {
logger.info(message)
} else {
logger.error(message)
}
})
server.on('listening', () => {
const address = server.address()
console.log(`Server listening ${address.address}:${address.port}`)
})
server.bind(PORT, HOST)

View File

@ -98,6 +98,8 @@ export const SETTINGS = {
TEST_EMAIL: mkString(), TEST_EMAIL: mkString(),
LS_WEBHOOK_SECRET: mkString(''), LS_WEBHOOK_SECRET: mkString(''),
SYSLOGD_PORT: mkNumber(6514),
} }
;(() => { ;(() => {
let passed = true let passed = true
@ -221,6 +223,8 @@ export const TEST_EMAIL = () => settings().TEST_EMAIL
export const LS_WEBHOOK_SECRET = () => settings().LS_WEBHOOK_SECRET export const LS_WEBHOOK_SECRET = () => settings().LS_WEBHOOK_SECRET
export const SYSLOGD_PORT = () => settings().SYSLOGD_PORT
/** Helpers */ /** Helpers */
export const MOTHERSHIP_DATA_ROOT = () => INSTANCE_DATA_ROOT(MOTHERSHIP_NAME()) export const MOTHERSHIP_DATA_ROOT = () => INSTANCE_DATA_ROOT(MOTHERSHIP_NAME())

View File

@ -25,8 +25,8 @@ export function InstanceLogger(instanceId: string, target: string) {
} }
const logDirectory = mkInstanceDataPath(instanceId, `logs`) const logDirectory = mkInstanceDataPath(instanceId, `logs`)
console.log(`Creating ${logDirectory}`)
if (!fs.existsSync(logDirectory)) { if (!fs.existsSync(logDirectory)) {
console.log(`Creating ${logDirectory}`)
fs.mkdirSync(logDirectory, { recursive: true }) fs.mkdirSync(logDirectory, { recursive: true })
} }

View File

@ -1,9 +1,10 @@
import { import {
APEX_DOMAIN, APEX_DOMAIN,
SYSLOGD_PORT,
mkContainerHomePath, mkContainerHomePath,
mkInstanceDataPath, mkInstanceDataPath,
} from '$constants' } from '$constants'
import { InstanceLogger, PortService } from '$services' import { PortService } from '$services'
import { import {
LoggerService, LoggerService,
SingletonBaseConfig, SingletonBaseConfig,
@ -19,6 +20,7 @@ import MemoryStream from 'memorystream'
import { gte } from 'semver' import { gte } from 'semver'
import { AsyncReturnType } from 'type-fest' import { AsyncReturnType } from 'type-fest'
import { PocketbaseReleaseVersionService } from '../PocketbaseReleaseVersionService' import { PocketbaseReleaseVersionService } from '../PocketbaseReleaseVersionService'
import { SyslogLogger } from '../SyslogService'
export type Env = { [_: string]: string } export type Env = { [_: string]: string }
export type SpawnConfig = { export type SpawnConfig = {
@ -92,7 +94,7 @@ export const createPocketbaseService = async (
} = _cfg } = _cfg
logger.breadcrumb(subdomain).breadcrumb(instanceId) logger.breadcrumb(subdomain).breadcrumb(instanceId)
const iLogger = InstanceLogger(instanceId, 'exec') const iLogger = SyslogLogger(instanceId, '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)
@ -106,20 +108,9 @@ export const createPocketbaseService = async (
const docker = new Docker() const docker = new Docker()
iLogger.info(`Starting instance`) iLogger.info(`Starting instance`)
const _stdoutData = (data: Buffer) => { const _stdoutData = (data: Buffer) => {}
const lines = data.toString().split(/\n/)
lines.forEach((line) => {
iLogger.info(line)
})
}
stdout.on('data', _stdoutData) stdout.on('data', _stdoutData)
const _stdErrData = (data: Buffer) => { const _stdErrData = (data: Buffer) => {}
const lines = data.toString().split(/\n/)
lines.forEach((line) => {
error(line)
iLogger.error(line)
})
}
stderr.on('data', _stdErrData) stderr.on('data', _stdErrData)
const Binds = [ const Binds = [
`${mkInstanceDataPath(instanceId)}:${mkContainerHomePath()}`, `${mkInstanceDataPath(instanceId)}:${mkContainerHomePath()}`,
@ -154,6 +145,13 @@ export const createPocketbaseService = async (
Hard: 4096, Hard: 4096,
}, },
], ],
LogConfig: {
Type: 'syslog',
Config: {
'syslog-address': `udp://localhost:${SYSLOGD_PORT()}`,
tag: instanceId,
},
},
}, },
Tty: false, Tty: false,
ExposedPorts: { ExposedPorts: {

View File

@ -0,0 +1,48 @@
import { SYSLOGD_PORT } from '$constants'
import { LoggerService } from '$shared'
import * as winston from 'winston'
import 'winston-syslog'
const loggers: {
[key: string]: {
info: (msg: string) => void
error: (msg: string) => void
}
} = {}
export function SyslogLogger(instanceId: string, target: string) {
const loggerKey = `${instanceId}_${target}`
if (loggers[loggerKey]) {
return loggers[loggerKey]!
}
const logger = winston.createLogger({
format: winston.format.printf((info) => {
return info.message
}),
transports: [
new winston.transports.Syslog({
host: `localhost`,
port: SYSLOGD_PORT(),
app_name: instanceId,
}),
],
})
const { error, warn } = LoggerService()
.create('SyslogLogger')
.breadcrumb(instanceId)
.breadcrumb(target)
const api = {
info: (msg: string) => {
logger.info(msg)
},
error: (msg: string) => {
logger.error(msg)
},
}
loggers[loggerKey] = api
return api
}