Files
MyIP/api/ipapi-is.js
T
jason5ng32andClaude Opus 4.7 833f66cc42 Feat(backend): introduce pino logger + pino-http request logs
Replace all backend console.* with a shared pino singleton at
common/logger.js. Startup-style lines get emoji prefixes (🚀 listening,
📦 maxmind ready, 📥 downloading, 🗓️ schedule, ⚠️ warning, ❌ failure);
per-handler / per-request logs stay plain.

Three .env-driven knobs, all without NODE_ENV:
  - LOG_LEVEL (default info) — pino level filter
  - LOG_FORMAT (default pretty) — "json" for log shippers
  - LOG_HTTP (default off) — pino-http on /api/* gated behind this flag
    so pm2 logs don't bloat with 2xx/3xx request lines on every deploy;
    when enabled, 4xx log as warn and 5xx as error

logger.js calls dotenv.config({ quiet: true }) itself because ES module
imports are hoisted above backend-server.js's dotenv call — without that
LOG_LEVEL from .env was silently ignored.

Fixes in common/rdap.js:
  - err: res.status → status: res.status (pino's err serializer expects
    an Error object, not a number)
  - broken single-quoted template literal in "No RDAP endpoint" message
  - demote "No RDAP endpoint" and "Domain not found" from error to warn
    (they're expected outcomes for user input, not server failures)

README (en/zh/fr/tr) + .env.example + AGENTS.md (root and api/) all
updated to document the three new vars and the "no console.* in backend"
convention.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:29:52 +08:00

41 lines
1.4 KiB
JavaScript

import { fetchUpstream } from '../common/fetch-with-timeout.js';
import logger from '../common/logger.js';
export default async (req, res) => {
// IP presence + validity guaranteed by requireValidIP middleware.
const ipAddress = req.query.ip;
const keys = (process.env.IPAPIIS_API_KEY).split(',');
const key = keys[Math.floor(Math.random() * keys.length)];
const url = `https://api.ipapi.is?q=${ipAddress}&key=${key}`;
try {
const apiRes = await fetchUpstream(url);
const json = await apiRes.json();
res.json(modifyJsonForIPAPI(json));
} catch (e) {
logger.error({ err: e, ip: ipAddress }, 'ipapi-is handler failed');
res.status(500).json({ error: e.message });
}
};
function modifyJsonForIPAPI(json) {
let asn = json.asn || {};
const { ip, location, is_datacenter, is_proxy, is_vpn, is_tor } = json;
return {
ip: ip,
city: location.city || 'N/A',
region: location.state || 'N/A',
country: location.country_code || 'N/A',
country_name: location.country || 'N/A',
country_code: location.country_code || 'N/A',
latitude: location.latitude || 'N/A',
longitude: location.longitude || 'N/A',
asn: asn.asn === undefined ? 'N/A' : 'AS' + asn.asn,
org: asn.org || 'N/A',
isHosting: is_datacenter || false,
isProxy: is_proxy || is_vpn || is_tor || false
};
}