Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
"dependencies": {
"@platformatic/composer": "^2.52.0",
"@platformatic/runtime": "^2.52.0",
"split2": "4.2.0",
"wattpm": "^2.52.0"
},
"devDependencies": {
"@types/split2": "4.2.3"
},
"workspaces": [
"web/*",
"external/*"
Expand Down
38 changes: 37 additions & 1 deletion web/backend/routes/root.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { FastifyInstance } from 'fastify'
import { JsonSchemaToTsProvider } from '@fastify/type-provider-json-schema-to-ts'
import { RuntimeApiClient } from '@platformatic/control'
import split2 from 'split2'
import { pipeline } from 'node:stream/promises'

export type Log = {
level: number,
time: number,
pid: number,
hostname: string,
msg: string
}

export default async function (fastify: FastifyInstance) {
const typedFastify = fastify.withTypeProvider<JsonSchemaToTsProvider>()
Expand Down Expand Up @@ -56,7 +66,33 @@ export default async function (fastify: FastifyInstance) {
schema: {
params: { type: 'object', properties: { pid: { type: 'number' } }, required: ['pid'] }
}
}, async (request) => api.getRuntimeAllLogsStream(request.params.pid))
}, async (request) => {
const readable = await api.getRuntimeAllLogsStream(request.params.pid)

const MAX_LOGS = 1000
const result: Log[] = []

await pipeline(
readable,
split2(),
async function * (source) {
for await (const line of source) {
try {
const parsedObject = JSON.parse(line)
result.push(parsedObject)

if (result.length > MAX_LOGS) {
result.shift()
}
} catch (err) {
fastify.log.warn({ line }, 'Invalid JSON line found:')
}
}
}
)

return result
})

typedFastify.get('/runtimes/:pid/openapi/:serviceId', {
schema: {
Expand Down
17 changes: 12 additions & 5 deletions web/backend/test/routes/root.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import test from 'node:test'
import assert from 'node:assert'
import { getServer, startWatt } from '../helper'
import type { Log } from '../../routes/root'

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

Expand Down Expand Up @@ -112,11 +113,17 @@ test('runtime is running', async (t) => {
})
assert.strictEqual(logs.statusCode, 200)

const [starting, listening, started, platformatic] = logs.body.trim().split('\n').filter(val => !val.includes('Loading envfile'))
assert.ok(starting.includes('Starting the service \\"backend\\"'))
assert.ok(listening.includes('Started the service \\"backend\\"'))
assert.ok(started.includes('Starting the service \\"frontend\\"'))
assert.ok(platformatic.includes('Started the service \\"frontend\\"'))
const result = logs.json<Log[]>()
assert.ok(result.some(({ msg }) => msg.includes('Starting the service')))
assert.ok(result.some(({ msg }) => msg.includes('Started the service')))
assert.ok(result.some(({ msg }) => msg.includes('Server listening at')))
assert.ok(result.some(({ msg }) => msg.includes('Platformatic is now listening')))

const [{ level, time, pid, hostname }] = result
assert.ok(typeof level, 'number')
assert.ok(typeof time, 'number')
assert.ok(typeof pid, 'number')
assert.ok(typeof hostname, 'string')
})

test('runtime restart', async (t) => {
Expand Down
4 changes: 2 additions & 2 deletions web/frontend/src/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export const getServices = async (id) => {

export const getLogs = async (id) => {
const result = await fetch(`${host}/runtimes/${id}/logs`)
const logs = await result.text()
return logs.trim().split('\n')
const data = await result.json()
return data
}

export const getApiMetricsPod = async (id) => {
Expand Down
4 changes: 2 additions & 2 deletions web/frontend/src/components/application-logs/AppLogs.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,10 @@ const AppLogs = React.forwardRef(({ filteredServices }, ref) => {
if (filterLogsByLevel || filteredServices.length >= 0) {
let founds = [...applicationLogs]
if (filterLogsByLevel) {
founds = founds.filter(log => JSON.parse(log).level >= filterLogsByLevel)
founds = founds.filter(log => log.level >= filterLogsByLevel)
}
if (filteredServices.length >= 0) {
founds = founds.filter(log => filteredServices.includes(JSON.parse(log).name))
founds = founds.filter(log => filteredServices.includes(log.name))
}
setFilteredLogs(founds)
} else {
Expand Down
2 changes: 1 addition & 1 deletion web/frontend/src/components/application-logs/Log.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import tooltipStyles from '~/styles/TooltipStyles.module.css'
function Log ({ log, onClickArrow }) {
const [displayJson, setDisplayJson] = useState(false)
const [logContainerClassName, setLogContainerClassName] = useState(normalClassName())
const { level, time, pid, name, msg, reqId, req, hostname, responseTime, ...rest } = JSON.parse(log)
const { level, time, pid, name, msg, reqId, req, hostname, responseTime, ...rest } = log
const levelDisplayed = getLevel(level)
let msgClassName = `${styles.msg} `
msgClassName += styles[`text${level}`]
Expand Down
Loading