File
Blob: src/workerd/server/tests/structured-logging/structured-logging-test.mjs
| 1 | // Copyright (c) 2025 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | import { spawn } from 'node:child_process'; |
| 5 | import { env } from 'node:process'; |
| 6 | import { test } from 'node:test'; |
| 7 | import assert from 'node:assert'; |
| 8 | |
| 9 | assert( |
| 10 | env.WORKERD_BINARY !== undefined, |
| 11 | 'You must set the WORKERD_BINARY environment variable.' |
| 12 | ); |
| 13 | assert( |
| 14 | env.WD_TEST_CONFIG !== undefined, |
| 15 | 'You must set the WD_TEST_CONFIG environment variable.' |
| 16 | ); |
| 17 | |
| 18 | test('structured logging produces valid JSON with required fields', async () => { |
| 19 | const result = await new Promise((resolve) => { |
| 20 | let output = ''; |
| 21 | const child = spawn(env.WORKERD_BINARY, ['test', env.WD_TEST_CONFIG], { |
| 22 | stdio: ['pipe', 'pipe', 'pipe'], |
| 23 | }); |
| 24 | |
| 25 | child.stdout.on('data', (data) => (output += data)); |
| 26 | child.stderr.on('data', (data) => (output += data)); |
| 27 | child.on('close', () => resolve(output)); |
| 28 | }); |
| 29 | |
| 30 | // Parse as JSON - extract the first JSON line |
| 31 | let logEntry; |
| 32 | assert.doesNotThrow(() => { |
| 33 | // Split by lines and find the first valid JSON line |
| 34 | const lines = result.split('\n'); |
| 35 | const jsonLine = lines.find((line) => line.trim().startsWith('{')); |
| 36 | assert(jsonLine, 'No JSON line found in output'); |
| 37 | logEntry = JSON.parse(jsonLine); |
| 38 | }, `Output is not valid JSON: ${result}`); |
| 39 | |
| 40 | // Verify required structure |
| 41 | assert.strictEqual( |
| 42 | typeof logEntry, |
| 43 | 'object', |
| 44 | 'Log entry should be an object' |
| 45 | ); |
| 46 | assert(logEntry !== null, 'Log entry should not be null'); |
| 47 | |
| 48 | // Verify required fields exist with correct types |
| 49 | assert( |
| 50 | typeof logEntry.level === 'string', |
| 51 | `level should be string, got: ${typeof logEntry.level}` |
| 52 | ); |
| 53 | assert( |
| 54 | typeof logEntry.message === 'string', |
| 55 | `message should be string, got: ${typeof logEntry.message}` |
| 56 | ); |
| 57 | assert( |
| 58 | typeof logEntry.timestamp === 'string', |
| 59 | `timestamp should be string, got: ${typeof logEntry.timestamp}` |
| 60 | ); |
| 61 | |
| 62 | // Verify timestamp is a valid ISO date |
| 63 | assert.doesNotThrow(() => { |
| 64 | new Date(logEntry.timestamp); |
| 65 | }, `timestamp should be valid date: ${logEntry.timestamp}`); |
| 66 | |
| 67 | // Verify error content is present in the message |
| 68 | assert( |
| 69 | logEntry.message.includes('SyntaxError'), |
| 70 | `Missing SyntaxError in message: ${logEntry.message}` |
| 71 | ); |
| 72 | assert( |
| 73 | logEntry.message.includes('javascript'), |
| 74 | `Missing javascript reference in message: ${logEntry.message}` |
| 75 | ); |
| 76 | }); |