Skip to content

Commit

Permalink
test: Add unit tests for prettifyLogs (#39)
Browse files Browse the repository at this point in the history
Co-authored-by: Lukas Jost <[email protected]>
  • Loading branch information
meyfa and lusu007 authored Jul 13, 2024
1 parent f854f8c commit 1aa4fd2
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 37 deletions.
38 changes: 1 addition & 37 deletions backend/src/api/pod-logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { FastifyPluginAsync } from 'fastify'
import { Controllers } from '../controllers.js'
import { badRequest, forbidden, notFound } from './errors.js'
import { authenticateSession } from '../auth/common.js'
import pinoPretty from 'pino-pretty'
import { Writable } from 'node:stream'
import { prettifyLogs } from '../renovate/prettify-logs.js'
import { enums, optional, type } from 'superstruct'
import { WeakCache } from '../util/cache.js'

Expand Down Expand Up @@ -49,38 +48,3 @@ export const podLogsRoute = ({ logsController }: Controllers): FastifyPluginAsyn
return logs
})
}

async function prettifyLogs (logs: string): Promise<string> {
// TODO: This consumes a lot of memory. We should use streaming instead.
// TODO: There should be a way for clients to request logs over WebSocket instead of polling.
const chunks: string[] = []
const prettyLogs = pinoPretty({
translateTime: true,
ignore: 'v,name,pid,hostname,logContext',
colorize: false,
destination: new Writable({
write (chunk, enc, cb) {
chunks.push(chunk.toString())
cb()
}
})
})
// Process the string in chunks to avoid blocking the event loop.
for (const chunk of chunked(logs)) {
await new Promise<void>((resolve) => {
if (!prettyLogs.write(chunk, 'utf8')) {
prettyLogs.once('drain', resolve)
} else {
setImmediate(resolve)
}
})
}
await new Promise<void>((resolve) => prettyLogs.end(resolve))
return chunks.join('')
}

function * chunked (str: string, size = 4096): Iterable<string> {
for (let i = 0; i < str.length; i += size) {
yield str.slice(i, i + size)
}
}
37 changes: 37 additions & 0 deletions backend/src/renovate/prettify-logs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pinoPretty from 'pino-pretty'
import { Writable } from 'node:stream'

export async function prettifyLogs (logs: string): Promise<string> {
// TODO: This consumes a lot of memory. We should use streaming instead.
// TODO: There should be a way for clients to request logs over WebSocket instead of polling.
const chunks: string[] = []
const prettyLogs = pinoPretty({
translateTime: true,
ignore: 'v,name,pid,hostname,logContext',
colorize: false,
destination: new Writable({
write (chunk, enc, cb) {
chunks.push(chunk.toString())
cb()
}
})
})
// Process the string in chunks to avoid blocking the event loop.
for (const chunk of chunked(logs)) {
await new Promise<void>((resolve) => {
if (!prettyLogs.write(chunk, 'utf8')) {
prettyLogs.once('drain', resolve)
} else {
setImmediate(resolve)
}
})
}
await new Promise<void>((resolve) => prettyLogs.end(resolve))
return chunks.join('')
}

function * chunked (str: string, size = 4096): Iterable<string> {
for (let i = 0; i < str.length; i += size) {
yield str.slice(i, i + size)
}
}
73 changes: 73 additions & 0 deletions backend/test/renovate/prettify-logs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import assert from 'node:assert'
import { prettifyLogs } from '../../src/renovate/prettify-logs.js'

describe('renovate/prettify-logs.ts', () => {
describe('prettifyLogs()', () => {
const oldTimezone = process.env.TZ

before(() => {
process.env.TZ = 'UTC'
})

after(() => {
process.env.TZ = oldTimezone
})

it('returns empty string for empty input', async () => {
const result = await prettifyLogs('')
assert.strictEqual(result, '')
})

it('returns the same string for non-JSON input', async () => {
const result = await prettifyLogs('hello world\nError: {"key":"value"}\n')
assert.strictEqual(result, 'hello world\nError: {"key":"value"}\n')
})

it('formats JSON logs', async () => {
const log = [
'{"name":"renovate","hostname":"renovate-foo-bar","pid":10,"level":30,"logContext":"abcd","msg":"test message","time":"2024-07-12T17:00:06.051Z","v":0}',
'{"name":"renovate","hostname":"renovate-foo-bar","pid":10,"level":40,"logContext":"abcd","foo":{"bar":"baz"},"qux":42,"msg":"another message","time":"2024-07-12T17:00:25.512Z","v":0}'
].join('\n')
const prettyLog = [
'[17:00:06.051] INFO: test message',
'[17:00:25.512] WARN: another message',
' foo: {',
' "bar": "baz"',
' }',
' qux: 42',
''
].join('\n')
const result = await prettifyLogs(log)
assert.strictEqual(result, prettyLog)
})

it('formats autodiscovery logs', async () => {
const log = '{"name":"renovate","hostname":"renovate-1337","pid":10,"level":30,"logContext":"abcd","length":4,"repositories":["foo/bar", "foo/baz/qux", "random", "stuff"],"msg":"Autodiscovered repositories","time":"2024-07-12T17:00:08.848Z","v":0}\n'
const prettyLog = [
'[17:00:08.848] INFO: Autodiscovered repositories',
' length: 4',
' repositories: [',
' "foo/bar",',
' "foo/baz/qux",',
' "random",',
' "stuff"',
' ]',
''
].join('\n')
const result = await prettifyLogs(log)
assert.strictEqual(result, prettyLog)
})

it('formats repository started', async () => {
const log = '{"name":"renovate","hostname":"renovate-1337","pid":10,"level":30,"logContext":"abcd","repository":"foo/bar/baz","renovateVersion":"12.345.6","msg":"Repository started","time":"2024-07-12T17:00:08.861Z","v":0}\n'
const prettyLog = [
'[17:00:08.861] INFO: Repository started',
' repository: "foo/bar/baz"',
' renovateVersion: "12.345.6"',
''
].join('\n')
const result = await prettifyLogs(log)
assert.strictEqual(result, prettyLog)
})
})
})

0 comments on commit 1aa4fd2

Please sign in to comment.