extractStructuredData($record); return match($this->format) { 'logfmt' => $this->formatAsLogfmt($data), 'kv' => $this->formatAsKeyValue($data), 'array' => $data, // Return raw array for further processing default => $this->formatAsLogfmt($data) }; } private function extractStructuredData(LogRecord $record): array { $data = []; if ($this->includeTimestamp) { $data['ts'] = $record->getTimestamp()->format('c'); } $data['level'] = $record->getLevel()->getName(); $data['msg'] = $record->getMessage(); if ($record->getChannel()) { $data['channel'] = $record->getChannel(); } // Flatten context $context = $record->getContext(); foreach ($context as $key => $value) { $data[$this->sanitizeKey($key)] = $this->sanitizeValue($value); } // Add extras $extras = $record->getExtras(); foreach ($extras as $key => $value) { $data[$this->sanitizeKey($key)] = $this->sanitizeValue($value); } return $data; } private function formatAsLogfmt(array $data): string { $pairs = []; foreach ($data as $key => $value) { if (is_string($value) && (str_contains($value, ' ') || str_contains($value, '='))) { $value = '"' . addslashes($value) . '"'; } $pairs[] = "{$key}={$value}"; } return implode(' ', $pairs); } private function formatAsKeyValue(array $data): string { $pairs = []; foreach ($data as $key => $value) { $pairs[] = "{$key}: {$value}"; } return implode(', ', $pairs); } private function sanitizeKey(string $key): string { // Remove internal prefixes and make key aggregation-friendly $key = ltrim($key, '_'); return preg_replace('/[^a-zA-Z0-9_]/', '_', $key); } private function sanitizeValue(mixed $value): string { return match(true) { is_string($value) => $value, is_numeric($value) => (string) $value, is_bool($value) => $value ? 'true' : 'false', is_null($value) => 'null', is_array($value) => json_encode($value, JSON_UNESCAPED_SLASHES), is_object($value) => get_class($value), default => 'unknown' }; } }