Add Health import failure diagnostics
This commit is contained in:
+153
-5
@@ -266,10 +266,14 @@ final class App
|
||||
}
|
||||
|
||||
$username = (string) ($user['username'] ?? '');
|
||||
$payload = request_json_body();
|
||||
$rawBody = (string) file_get_contents('php://input');
|
||||
$payload = $this->decodeHealthImportPayload($rawBody);
|
||||
if ($payload === []) {
|
||||
$this->users->recordHealthImport($username, 'error', 'Leerer oder ungültiger JSON-Import.');
|
||||
json_response(['ok' => false, 'message' => 'Leerer oder ungültiger JSON-Import.'], 400);
|
||||
$traceID = $this->healthImportTraceID();
|
||||
$message = 'Diagnose-ID: ' . $traceID . '. Leerer oder ungültiger JSON-Import.';
|
||||
$this->logHealthImportFailure($traceID, $username, 'Leerer oder ungültiger JSON-Import.', [], strlen($rawBody));
|
||||
$this->users->recordHealthImport($username, 'error', $message);
|
||||
json_response(['ok' => false, 'message' => $message], 400);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -285,11 +289,55 @@ final class App
|
||||
$this->users->recordHealthImport($username, 'ok', $message);
|
||||
json_response(['ok' => true, 'message' => $message, 'result' => $result]);
|
||||
} catch (RuntimeException $exception) {
|
||||
$this->users->recordHealthImport($username, 'error', $exception->getMessage());
|
||||
json_response(['ok' => false, 'message' => $exception->getMessage()], 400);
|
||||
$traceID = $this->healthImportTraceID();
|
||||
$message = 'Diagnose-ID: ' . $traceID . '. ' . $exception->getMessage();
|
||||
$this->logHealthImportFailure($traceID, $username, $exception->getMessage(), $payload, strlen($rawBody));
|
||||
$this->users->recordHealthImport($username, 'error', $message);
|
||||
json_response(['ok' => false, 'message' => $message], 400);
|
||||
}
|
||||
}
|
||||
|
||||
private function decodeHealthImportPayload(string $rawBody): array
|
||||
{
|
||||
if (trim($rawBody) === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($rawBody, true);
|
||||
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
private function healthImportTraceID(): string
|
||||
{
|
||||
try {
|
||||
return substr(bin2hex(random_bytes(4)), 0, 8);
|
||||
} catch (Exception) {
|
||||
return substr(sha1((string) microtime(true)), 0, 8);
|
||||
}
|
||||
}
|
||||
|
||||
private function logHealthImportFailure(string $traceID, string $username, string $message, array $payload, int $rawLength): void
|
||||
{
|
||||
$metrics = $payload === [] ? [] : $this->healthMetricsFromPayload($payload);
|
||||
$workouts = $payload === [] ? [] : $this->healthWorkoutsFromPayload($payload);
|
||||
$context = [
|
||||
'trace_id' => $traceID,
|
||||
'user' => $username,
|
||||
'message' => $message,
|
||||
'method' => (string) ($_SERVER['REQUEST_METHOD'] ?? ''),
|
||||
'path' => (string) ($_SERVER['REQUEST_URI'] ?? ''),
|
||||
'content_type' => (string) ($_SERVER['CONTENT_TYPE'] ?? ($_SERVER['HTTP_CONTENT_TYPE'] ?? '')),
|
||||
'content_length' => (string) ($_SERVER['CONTENT_LENGTH'] ?? ''),
|
||||
'raw_length' => $rawLength,
|
||||
'json_error' => json_last_error_msg(),
|
||||
'user_agent' => (string) ($_SERVER['HTTP_USER_AGENT'] ?? ''),
|
||||
'payload' => $this->healthPayloadDiagnostics($payload, $metrics, $workouts),
|
||||
];
|
||||
|
||||
error_log('[Mood Health Import] ' . json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
private function handleHealthImportStatus(): void
|
||||
{
|
||||
$user = $this->requireUser();
|
||||
@@ -569,6 +617,106 @@ final class App
|
||||
return implode('. ', $parts) . '.';
|
||||
}
|
||||
|
||||
private function healthPayloadDiagnostics(array $payload, array $metrics, array $workouts): array
|
||||
{
|
||||
return [
|
||||
'top_level' => $this->describeHealthPayloadNode($payload),
|
||||
'metrics_count' => count($metrics),
|
||||
'workouts_count' => count($workouts),
|
||||
'metric_samples' => $this->healthMetricDiagnostics($metrics),
|
||||
'workout_samples' => $this->healthArraySamples($workouts),
|
||||
'payload_samples' => $this->healthNestedArraySamples($payload),
|
||||
];
|
||||
}
|
||||
|
||||
private function healthMetricDiagnostics(array $metrics): array
|
||||
{
|
||||
$samples = [];
|
||||
foreach (array_slice($metrics, 0, 8) as $index => $metric) {
|
||||
if (!is_array($metric)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = is_array($metric['data'] ?? null) ? $metric['data'] : [];
|
||||
$firstPoint = null;
|
||||
foreach ($data as $point) {
|
||||
if (is_array($point)) {
|
||||
$firstPoint = $point;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$samples[] = [
|
||||
'index' => $index,
|
||||
'name' => (string) ($metric['name'] ?? ''),
|
||||
'normalized' => $this->healthMetricName((string) ($metric['name'] ?? '')),
|
||||
'data_count' => count($data),
|
||||
'keys' => array_slice(array_keys($metric), 0, 12),
|
||||
'first_point' => is_array($firstPoint) ? $this->describeHealthPayloadNode($firstPoint) : null,
|
||||
];
|
||||
}
|
||||
|
||||
return $samples;
|
||||
}
|
||||
|
||||
private function healthArraySamples(array $items): array
|
||||
{
|
||||
$samples = [];
|
||||
foreach (array_slice($items, 0, 5) as $index => $item) {
|
||||
if (is_array($item)) {
|
||||
$samples[] = ['index' => $index] + $this->describeHealthPayloadNode($item);
|
||||
}
|
||||
}
|
||||
|
||||
return $samples;
|
||||
}
|
||||
|
||||
private function healthNestedArraySamples(array $payload, string $path = '$', int $depth = 0): array
|
||||
{
|
||||
if ($depth > 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$samples = [];
|
||||
foreach ($payload as $key => $value) {
|
||||
$currentPath = $path . (is_int($key) ? '[' . $key . ']' : '.' . (string) $key);
|
||||
if (!is_array($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$samples[] = ['path' => $currentPath] + $this->describeHealthPayloadNode($value);
|
||||
if (count($samples) >= 12) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($this->healthNestedArraySamples($value, $currentPath, $depth + 1) as $nested) {
|
||||
$samples[] = $nested;
|
||||
if (count($samples) >= 12) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $samples;
|
||||
}
|
||||
|
||||
private function describeHealthPayloadNode(array $node): array
|
||||
{
|
||||
$keys = array_slice(array_keys($node), 0, 12);
|
||||
$types = [];
|
||||
foreach ($keys as $key) {
|
||||
$value = $node[$key] ?? null;
|
||||
$types[(string) $key] = is_array($value) ? 'array(' . count($value) . ')' : get_debug_type($value);
|
||||
}
|
||||
|
||||
return [
|
||||
'is_list' => array_is_list($node),
|
||||
'count' => count($node),
|
||||
'keys' => $keys,
|
||||
'types' => $types,
|
||||
];
|
||||
}
|
||||
|
||||
private function healthEventsFromMetrics(array $metrics): array
|
||||
{
|
||||
$steps = [];
|
||||
|
||||
Reference in New Issue
Block a user