sys_get_temp_dir(), 'MESHVAULT_DB_HOST' => 'db', 'MESHVAULT_DB_NAME' => 'meshvault', 'MESHVAULT_DB_USER' => 'meshvault', 'MESHVAULT_DB_PASSWORD' => 'private-secret', 'MESHVAULT_LOG_LEVEL' => 'warning', ]); $base = \MeshVault\Logging\LoggerFactory::create($config); $a = \MeshVault\Logging\LoggerFactory::forJob($base, 'job-a'); $b = \MeshVault\Logging\LoggerFactory::forJob($base, 'job-b'); $base->info('filtered'); $a->debug('filtered job'); $a->warning("first\nline", ['file' => 'part.stl', 'job_id' => 'spoofed', 'config' => $config]); $b->error('second', ['count' => 2]); $base->warning('base'); $a->critical('again'); \MeshVault\Logging\LoggerFactory::forJob($a, 'job-c')->alert('nested'); PHP; $process = proc_open([PHP_BINARY], [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']], $pipes); self::assertIsResource($process); fwrite($pipes[0], $code); fclose($pipes[0]); $stdout = stream_get_contents($pipes[1]); $stderr = stream_get_contents($pipes[2]); fclose($pipes[1]); fclose($pipes[2]); self::assertSame(0, proc_close($process), $stderr); self::assertSame('', $stderr); self::assertStringNotContainsString('private-secret', $stdout); self::assertStringNotContainsString('filtered', $stdout); $lines = explode("\n", rtrim($stdout, "\r\n")); self::assertCount(5, $lines); $records = array_map( static fn (string $line): array => json_decode($line, true, 512, JSON_THROW_ON_ERROR), $lines, ); self::assertSame("first\nline", $records[0]['message']); self::assertSame('WARNING', $records[0]['level_name']); self::assertSame('job-a', $records[0]['context']['job_id']); self::assertSame('part.stl', $records[0]['context']['file']); self::assertSame('job-b', $records[1]['context']['job_id']); self::assertSame(2, $records[1]['context']['count']); self::assertArrayNotHasKey('job_id', $records[2]['context']); self::assertSame('job-a', $records[3]['context']['job_id']); self::assertSame('job-c', $records[4]['context']['job_id']); self::assertArrayHasKey('datetime', $records[0]); } public function testJobScopeWorksWithAnyPsrLoggerAndRetainsMessage(): void { self::assertTrue(class_exists(LoggerFactory::class), 'LoggerFactory must exist'); $base = new class extends AbstractLogger { public array $records = []; public function log($level, string|Stringable $message, array $context = []): void { $this->records[] = [$level, $message, $context]; } }; $message = new class implements Stringable { public function __toString(): string { return 'message'; } }; LoggerFactory::forJob($base, 'job-a')->notice($message, ['file' => 'part.stl']); self::assertSame(['notice', $message, ['file' => 'part.stl', 'job_id' => 'job-a']], $base->records[0]); } }