feat(logging): add ipAddress to default log metadata

Add getLocalIpAddress() that reliably resolves the primary LAN IPv4
address by collecting all non-loopback, non-APIPA addresses and
prioritizing RFC 1918 private ranges (192.168.x.x, 10.x.x.x,
172.16-31.x.x) over public IPs. Falls back to any non-internal
address or 'N/A'.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-04-04 17:52:11 +08:00
parent 8be5a2763d
commit 7f38150d0a

View File

@@ -34,6 +34,52 @@ const PROJECT_LEVELS = {
// Cache isProduction() at module load — app.isPackaged never changes at runtime
const IS_PROD = isProduction()
/**
* Check if an IPv4 address is an RFC 1918 private address.
* Private ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
*/
function isPrivateIpv4(ip: string): boolean {
const parts = ip.split('.').map(Number)
if (parts.length !== 4) return false
// 10.0.0.0/8
if (parts[0] === 10) return true
// 172.16.0.0/12
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true
// 192.168.0.0/16
if (parts[0] === 192 && parts[1] === 168) return true
return false
}
/**
* Get the primary local IPv4 address (RFC 1918 private address).
*
* Strategy:
* 1. Collect all non-internal, non-APIPA IPv4 addresses from physical adapters
* 2. Return the first private (LAN) address found
* 3. Fallback to any remaining non-internal address
* 4. Returns 'N/A' if none found
*/
function getLocalIpAddress(): string {
const interfaces = os.networkInterfaces()
const candidates: string[] = []
for (const [, addrs] of Object.entries(interfaces)) {
if (!addrs) continue
for (const iface of addrs) {
if (iface.family === 'IPv4' && !iface.internal && !iface.address.startsWith('169.254.')) {
candidates.push(iface.address)
}
}
}
// Prefer private (LAN) addresses
const privateIp = candidates.find(isPrivateIpv4)
if (privateIp) return privateIp
// Fallback: any non-internal address
return candidates[0] || 'N/A'
}
/**
* Check if an error has already been serialized (plain object with name/message but not an Error instance).
* Prevents double-serialization when logError() output passes through the format pipeline.
@@ -172,7 +218,8 @@ const logger = winston.createLogger({
defaultMeta: {
service: 'erpauto',
appVersion: app.getVersion(),
computerName: os.hostname()
computerName: os.hostname(),
ipAddress: getLocalIpAddress()
},
transports: [
// Console transport - always enabled