feat: Add Pino structured logging, release-please CI/CD and Docker pipeline
Replace all console.log/error with Pino logger (info/warn/error/debug/fatal) for structured JSON logging in production and pretty-print in development. Remove redundant header dumps and consolidate rate-limit logging. Add GitHub Actions workflow with release-please for automated semver releases and Docker build/push to GHCR on new releases.
This commit is contained in:
@@ -2,12 +2,13 @@ const { db } = require('../db');
|
||||
const { getPlayerFromDB, getRoundHistoryFromDB, savePredictedRatingToDB, savePlayerToDB } = require('../models/player');
|
||||
const { fetchPlayerDataHTTP, parsePlayerData } = require('../scrapers/player-http');
|
||||
const { calculatePredictedRating } = require('./rating-calculator');
|
||||
const logger = require('../logger');
|
||||
|
||||
async function getPlayerDataFromDB(pdgaNumber) {
|
||||
try {
|
||||
const cachedPlayer = await getPlayerFromDB(pdgaNumber);
|
||||
if (cachedPlayer) {
|
||||
console.log(`Loading PDGA ${pdgaNumber} from DB (source of truth)`);
|
||||
logger.debug(`Loading PDGA ${pdgaNumber} from DB (source of truth)`);
|
||||
|
||||
let predictedRating = cachedPlayer.predicted_rating;
|
||||
let stdDev = cachedPlayer.std_dev;
|
||||
@@ -28,33 +29,33 @@ async function getPlayerDataFromDB(pdgaNumber) {
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.error(`Database error for PDGA ${pdgaNumber}:`, err.message);
|
||||
logger.error(`Database error for PDGA ${pdgaNumber}:`, err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function scrapePDGARating(pdgaNumber, retries = 3) {
|
||||
console.log(`=== Refreshing PDGA ${pdgaNumber} from PDGA website ===`);
|
||||
logger.info(`Refreshing PDGA ${pdgaNumber} from PDGA website`);
|
||||
|
||||
for (let attempt = 1; attempt <= retries; attempt++) {
|
||||
try {
|
||||
console.log(`Attempt ${attempt}/${retries} for PDGA ${pdgaNumber} (using HTTP)`);
|
||||
logger.info(`Attempt ${attempt}/${retries} for PDGA ${pdgaNumber} (using HTTP)`);
|
||||
|
||||
const html = await fetchPlayerDataHTTP(pdgaNumber);
|
||||
const result = parsePlayerData(html, pdgaNumber);
|
||||
|
||||
try {
|
||||
await savePlayerToDB(result);
|
||||
console.log(`Saved PDGA ${pdgaNumber} to database`);
|
||||
logger.info(`Saved PDGA ${pdgaNumber} to database`);
|
||||
} catch (dbErr) {
|
||||
console.error(`Failed to save PDGA ${pdgaNumber} to database:`, dbErr.message);
|
||||
logger.error(`Failed to save PDGA ${pdgaNumber} to database:`, dbErr.message);
|
||||
}
|
||||
|
||||
console.log(`Successfully scraped PDGA ${pdgaNumber} on attempt ${attempt}`);
|
||||
logger.info(`Successfully scraped PDGA ${pdgaNumber} on attempt ${attempt}`);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Attempt ${attempt}/${retries} failed for PDGA ${pdgaNumber}:`, error.message);
|
||||
logger.error(`Attempt ${attempt}/${retries} failed for PDGA ${pdgaNumber}:`, error.message);
|
||||
|
||||
if (attempt === retries) {
|
||||
return {
|
||||
@@ -72,13 +73,13 @@ async function scrapePDGARating(pdgaNumber, retries = 3) {
|
||||
const retryAfter = error.rateLimitInfo.headers['retry-after'];
|
||||
if (retryAfter) {
|
||||
retryDelay = Math.max(retryDelay, (parseInt(retryAfter) + 1) * 1000);
|
||||
console.log(`Using Retry-After header: waiting ${retryDelay/1000}s`);
|
||||
logger.warn(`Using Retry-After header: waiting ${retryDelay/1000}s`);
|
||||
}
|
||||
}
|
||||
|
||||
if (error.code === 'ECONNRESET') {
|
||||
retryDelay = Math.max(retryDelay, 10000);
|
||||
console.log(`Connection reset detected: waiting ${retryDelay/1000}s`);
|
||||
logger.warn(`Connection reset detected: waiting ${retryDelay/1000}s`);
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
@@ -90,7 +91,7 @@ async function getPredictedRatingFromDB(pdgaNumber) {
|
||||
try {
|
||||
const roundHistory = await getRoundHistoryFromDB(pdgaNumber);
|
||||
if (roundHistory.length > 0) {
|
||||
console.log(`Using ${roundHistory.length} cached rounds for PDGA ${pdgaNumber} prediction`);
|
||||
logger.debug(`Using ${roundHistory.length} cached rounds for PDGA ${pdgaNumber} prediction`);
|
||||
|
||||
const roundRatings = roundHistory.map(round => ({
|
||||
rating: round.rating,
|
||||
@@ -106,7 +107,7 @@ async function getPredictedRatingFromDB(pdgaNumber) {
|
||||
}
|
||||
return 0;
|
||||
} catch (err) {
|
||||
console.error(`Error getting predicted rating from DB for ${pdgaNumber}:`, err.message);
|
||||
logger.error(`Error getting predicted rating from DB for ${pdgaNumber}:`, err.message);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -124,7 +125,7 @@ async function getAllRatingsFromDB(progressCallback = null) {
|
||||
);
|
||||
});
|
||||
|
||||
console.log(`Loading ${allPlayers.length} players from database...`);
|
||||
logger.info(`Loading ${allPlayers.length} players from database...`);
|
||||
|
||||
const ratings = [];
|
||||
const total = allPlayers.length;
|
||||
@@ -159,7 +160,7 @@ async function getAllRatingsFromDB(progressCallback = null) {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to load PDGA ${pdgaNumber} from database:`, error.message);
|
||||
logger.error(`Failed to load PDGA ${pdgaNumber} from database:`, error.message);
|
||||
const errorData = {
|
||||
pdgaNumber: parseInt(pdgaNumber),
|
||||
name: player.name || 'Database Error',
|
||||
@@ -183,7 +184,7 @@ async function getAllRatingsFromDB(progressCallback = null) {
|
||||
|
||||
return ratings.sort((a, b) => (b.rating || 0) - (a.rating || 0));
|
||||
} catch (error) {
|
||||
console.error('Error loading players from database:', error);
|
||||
logger.error('Error loading players from database:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -201,7 +202,7 @@ async function refreshAllPlayersInDB(progressCallback = null) {
|
||||
);
|
||||
});
|
||||
|
||||
console.log(`Refreshing ${allPlayers.length} players from database...`);
|
||||
logger.info(`Refreshing ${allPlayers.length} players from database...`);
|
||||
|
||||
const ratings = [];
|
||||
const total = allPlayers.length;
|
||||
@@ -210,7 +211,7 @@ async function refreshAllPlayersInDB(progressCallback = null) {
|
||||
const player = allPlayers[i];
|
||||
const pdgaNumber = player.pdga_number;
|
||||
|
||||
console.log(`Refreshing PDGA ${pdgaNumber}... (${i + 1}/${total})`);
|
||||
logger.info(`Refreshing PDGA ${pdgaNumber}... (${i + 1}/${total})`);
|
||||
|
||||
if (progressCallback) {
|
||||
progressCallback({
|
||||
@@ -237,7 +238,7 @@ async function refreshAllPlayersInDB(progressCallback = null) {
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
} catch (error) {
|
||||
console.error(`Failed to refresh PDGA ${pdgaNumber}:`, error.message);
|
||||
logger.error(`Failed to refresh PDGA ${pdgaNumber}:`, error.message);
|
||||
const errorData = {
|
||||
pdgaNumber: parseInt(pdgaNumber),
|
||||
name: player.name || 'Error',
|
||||
@@ -261,7 +262,7 @@ async function refreshAllPlayersInDB(progressCallback = null) {
|
||||
|
||||
return ratings.sort((a, b) => (b.rating || 0) - (a.rating || 0));
|
||||
} catch (error) {
|
||||
console.error('Error refreshing all players:', error);
|
||||
logger.error('Error refreshing all players:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user