Add build info metric and diagnostic logging for Garmin sync
All checks were successful
Deploy / deploy (push) Successful in 1m44s

- Add phaseflow_build_info metric with version and commit labels
- Inject GIT_COMMIT env var at build time via next.config.ts
- Add logging to all Garmin fetch functions (HRV, body battery, intensity)
- Log API response status codes, actual data values, and errors

This enables verifying which build is deployed and diagnosing
silent failures where Garmin API returns errors but sync reports success.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-15 13:09:29 +00:00
parent 7ed827f82c
commit 85b535f04a
3 changed files with 87 additions and 4 deletions

View File

@@ -1,7 +1,22 @@
// ABOUTME: Next.js configuration for PhaseFlow application.
// ABOUTME: Configures standalone output and injects git commit hash for build verification.
import { execSync } from "node:child_process";
import type { NextConfig } from "next";
// Get git commit hash at build time for deployment verification
function getGitCommit(): string {
try {
return execSync("git rev-parse --short HEAD").toString().trim();
} catch {
return "unknown";
}
}
const nextConfig: NextConfig = {
output: "standalone",
env: {
GIT_COMMIT: process.env.GIT_COMMIT || getGitCommit(),
},
};
export default nextConfig;

View File

@@ -1,5 +1,7 @@
// ABOUTME: Garmin Connect API client using stored OAuth tokens.
// ABOUTME: Fetches body battery, HRV, and intensity minutes from Garmin.
import { logger } from "@/lib/logger";
import type { GarminTokens, HrvStatus } from "@/types";
const GARMIN_BASE_URL = "https://connect.garmin.com/modern/proxy";
@@ -63,6 +65,10 @@ export async function fetchHrvStatus(
});
if (!response.ok) {
logger.warn(
{ status: response.status, endpoint: "hrv-service" },
"Garmin HRV API error",
);
return "Unknown";
}
@@ -70,13 +76,23 @@ export async function fetchHrvStatus(
const status = data?.hrvSummary?.status;
if (status === "BALANCED") {
logger.info({ status: "BALANCED" }, "Garmin HRV data received");
return "Balanced";
}
if (status === "UNBALANCED") {
logger.info({ status: "UNBALANCED" }, "Garmin HRV data received");
return "Unbalanced";
}
logger.info(
{ rawStatus: status, hasData: !!data?.hrvSummary },
"Garmin HRV returned unknown status",
);
return "Unknown";
} catch {
} catch (error) {
logger.error(
{ err: error, endpoint: "hrv-service" },
"Garmin HRV fetch failed",
);
return "Unknown";
}
}
@@ -97,6 +113,10 @@ export async function fetchBodyBattery(
);
if (!response.ok) {
logger.warn(
{ status: response.status, endpoint: "bodyBattery" },
"Garmin body battery API error",
);
return { current: null, yesterdayLow: null };
}
@@ -108,8 +128,22 @@ export async function fetchBodyBattery(
const yesterdayStats = data?.bodyBatteryStatList?.[0];
const yesterdayLow = yesterdayStats?.min ?? null;
logger.info(
{
current,
yesterdayLow,
hasCurrentData: !!currentData,
hasYesterdayData: !!yesterdayStats,
},
"Garmin body battery data received",
);
return { current, yesterdayLow };
} catch {
} catch (error) {
logger.error(
{ err: error, endpoint: "bodyBattery" },
"Garmin body battery fetch failed",
);
return { current: null, yesterdayLow: null };
}
}
@@ -129,6 +163,10 @@ export async function fetchIntensityMinutes(
);
if (!response.ok) {
logger.warn(
{ status: response.status, endpoint: "fitnessstats" },
"Garmin intensity minutes API error",
);
return 0;
}
@@ -136,14 +174,28 @@ export async function fetchIntensityMinutes(
const weeklyTotal = data?.weeklyTotal;
if (!weeklyTotal) {
logger.info(
{ hasWeeklyTotal: false },
"Garmin intensity minutes: no weekly data",
);
return 0;
}
const moderate = weeklyTotal.moderateIntensityMinutes ?? 0;
const vigorous = weeklyTotal.vigorousIntensityMinutes ?? 0;
const total = moderate + vigorous;
return moderate + vigorous;
} catch {
logger.info(
{ moderate, vigorous, total },
"Garmin intensity minutes data received",
);
return total;
} catch (error) {
logger.error(
{ err: error, endpoint: "fitnessstats" },
"Garmin intensity minutes fetch failed",
);
return 0;
}
}

View File

@@ -47,3 +47,19 @@ export const activeUsersGauge = new promClient.Gauge({
help: "Number of users with activity in the last 24 hours",
registers: [metricsRegistry],
});
// Build info metric: exposes version and git commit for deployment verification
const buildInfo = new promClient.Gauge({
name: "phaseflow_build_info",
help: "Build information with version and git commit",
labelNames: ["version", "commit"] as const,
registers: [metricsRegistry],
});
// Set build info at module load (value is always 1, labels contain the info)
buildInfo
.labels({
version: process.env.npm_package_version || "unknown",
commit: process.env.GIT_COMMIT || "unknown",
})
.set(1);