refactor: add logging configuration with adjustable log levels and update related documentation

This commit is contained in:
Tudor Stanciu 2025-10-05 00:20:20 +03:00
parent 66141c696b
commit c546a3c1a0
10 changed files with 137 additions and 28 deletions

1
.env
View File

@ -13,6 +13,7 @@ PORT=5172
BASE_PATH=/
NODE_ENV=development
ENABLE_HTTPS_SECURITY=false
LOG_LEVEL=debug
# Database Path - Local MaxMind databases location
MAXMIND_DB_PATH=D:\\tools\\maxmind-dbs

View File

@ -15,6 +15,14 @@ PORT=5172
BASE_PATH=/
NODE_ENV=production
# Logging Configuration
# Minimum log level: debug, info, warning, error
# - debug: All logs (very verbose)
# - info: Info, warnings, and errors (default for development)
# - warning: Only warnings and errors (recommended for production)
# - error: Only errors
LOG_LEVEL=warning
# Database Path (usually doesn't need to change)
MAXMIND_DB_PATH=/usr/share/maxmind

View File

@ -208,8 +208,8 @@
"**HTTPS Security Headers** - Toggle security headers (CSP, HSTS, COOP, CORP) based on deployment type (HTTP vs HTTPS)",
"**Port Configuration** - Customize backend and frontend ports",
"**Base Path Support** - Deploy under custom URL paths for reverse proxy scenarios",
"**Logging Levels** - Adjust verbosity from debug to production",
"**Seq Integration** - Optional structured logging to Seq server",
"**Configurable Logging Levels** - Hierarchical log filtering (debug, info, warning, error) similar to Serilog in .NET",
"**Seq Integration** - Optional structured logging to Seq server with configurable minimum level",
"**Database Path** - Configurable GeoLite2 database location"
]
},
@ -217,12 +217,14 @@
"title": "Monitoring & Observability",
"items": [
"**Structured Logging** - JSON-formatted logs with context and correlation IDs",
"**Hierarchical Log Levels** - Configurable minimum log level (debug, info, warning, error) for filtering",
"**Version Endpoint** - Check deployed version, build date, and git revision",
"**Health Checks** - Monitor service availability and database status",
"**Error Tracking** - Detailed error messages with stack traces in development",
"**Request Logging** - Log all API requests with IP, method, path, and response time",
"**Rate Limit Metrics** - Track rate limit hits and blocked requests",
"**Seq Integration** - Optional centralized logging with query and dashboard capabilities"
"**Seq Integration** - Optional centralized logging with query and dashboard capabilities",
"**Production-Ready Logging** - Minimal overhead with filtered log levels for production environments"
]
},
{

View File

@ -51,6 +51,7 @@ PORT=5172
BASE_PATH=/
NODE_ENV=development
ENABLE_HTTPS_SECURITY=false
LOG_LEVEL=debug # debug | info | warning | error
# Database Path - Point to your MaxMind databases location
MAXMIND_DB_PATH=D:\tools\maxmind-dbs # Windows
@ -98,13 +99,14 @@ This will start:
#### 1. Configure Environment
Edit `.env` file with your MaxMind credentials:
Edit `.env` file with your MaxMind credentials and logging level:
```env
GEOIPUPDATE_ACCOUNT_ID=your_account_id_here
GEOIPUPDATE_LICENSE_KEY=your_license_key_here
FRONTEND_API_KEY=your-secure-frontend-api-key-here
EXTERNAL_API_KEYS=your-secure-external-api-key-1,your-secure-external-api-key-2
LOG_LEVEL=warning # Recommended for production (debug | info | warning | error)
```
#### 2. Start Services
@ -113,6 +115,28 @@ EXTERNAL_API_KEYS=your-secure-external-api-key-1,your-secure-external-api-key-2
docker-compose up -d
```
**Or with Docker run:**
```bash
# Build image
docker build -t bitip-geoip:latest .
# Run with custom log level
docker run -d \
-p 5172:5172 \
-e LOG_LEVEL=warning \
-e FRONTEND_API_KEY=your-api-key \
-v /path/to/maxmind-dbs:/usr/share/maxmind \
bitip-geoip:latest
```
**Available log levels:**
- `debug` - All logs (verbose, for development/troubleshooting)
- `info` - Info + warnings + errors (default)
- `warning` - Warnings + errors only (recommended for production)
- `error` - Errors only (minimal logging)
#### 3. Access the Service
- **Web Interface**: http://localhost:5172
@ -323,24 +347,24 @@ npm run install:all # Install all dependencies (root + backend + frontend)
### Environment Variables
| Variable | Default | Description |
| -------------------------- | ---------------------- | --------------------------------------------------------------- |
| `PORT` | `5172` | Server port |
| `BASE_PATH` | `/` | Application base path |
| `NODE_ENV` | `development` | Environment mode (development/production) |
| `ENABLE_HTTPS_SECURITY` | `false` | Enable HTTPS security headers (CSP, HSTS, COOP, CORP) |
| `MAXMIND_DB_PATH` | `/usr/share/maxmind` | MaxMind database directory |
| `FRONTEND_API_KEY` | `frontend-default-key` | Frontend API key |
| `EXTERNAL_API_KEYS` | `external-default-key` | External API keys (comma-separated) |
| `FRONTEND_ALLOWED_ORIGINS` | `http://localhost:*` | Allowed CORS origins for frontend (comma-separated) |
| `FRONTEND_RATE_WINDOW_MS` | `60000` | Frontend rate limit window (milliseconds) |
| `FRONTEND_RATE_MAX` | `30` | Frontend rate limit max requests |
| `EXTERNAL_RATE_WINDOW_MS` | `60000` | External rate limit window (milliseconds) |
| `EXTERNAL_RATE_MAX` | `1000` | External rate limit max requests |
| `BATCH_LIMIT` | `100` | Maximum IPs per batch request |
| `DEBOUNCE_MS` | `2000` | Frontend input debounce delay (milliseconds) |
| `SEQ_URL` | - | Seq logging server URL (optional) |
| `SEQ_API_KEY` | - | Seq API key (optional) |
| Variable | Default | Description |
| -------------------------- | ---------------------- | ----------------------------------------------------- |
| `PORT` | `5172` | Server port |
| `BASE_PATH` | `/` | Application base path |
| `NODE_ENV` | `development` | Environment mode (development/production) |
| `ENABLE_HTTPS_SECURITY` | `false` | Enable HTTPS security headers (CSP, HSTS, COOP, CORP) |
| `MAXMIND_DB_PATH` | `/usr/share/maxmind` | MaxMind database directory |
| `FRONTEND_API_KEY` | `frontend-default-key` | Frontend API key |
| `EXTERNAL_API_KEYS` | `external-default-key` | External API keys (comma-separated) |
| `FRONTEND_ALLOWED_ORIGINS` | `http://localhost:*` | Allowed CORS origins for frontend (comma-separated) |
| `FRONTEND_RATE_WINDOW_MS` | `60000` | Frontend rate limit window (milliseconds) |
| `FRONTEND_RATE_MAX` | `30` | Frontend rate limit max requests |
| `EXTERNAL_RATE_WINDOW_MS` | `60000` | External rate limit window (milliseconds) |
| `EXTERNAL_RATE_MAX` | `1000` | External rate limit max requests |
| `BATCH_LIMIT` | `100` | Maximum IPs per batch request |
| `DEBOUNCE_MS` | `2000` | Frontend input debounce delay (milliseconds) |
| `SEQ_URL` | - | Seq logging server URL (optional) |
| `SEQ_API_KEY` | - | Seq API key (optional) |
### MaxMind Database Setup

View File

@ -120,6 +120,25 @@ ENABLE_HTTPS_SECURITY=false
- `production` - Optimized for performance, minimal logging
- **Example**: `production`
**`LOG_LEVEL`**
- **Purpose**: Minimum logging level for both console and Seq output
- **Format**: String
- **Required**: No
- **Default**: `info`
- **Valid Values**:
- `debug` - All logs including debug messages (very verbose, use only for troubleshooting)
- `info` - Informational messages, warnings, and errors (recommended for development)
- `warning` - Only warnings and errors (recommended for production)
- `error` - Only error messages (minimal logging)
- **Use Cases**:
- Development: `debug` or `info` - Full visibility into application behavior
- Production: `warning` or `error` - Reduce log volume and focus on issues
- Troubleshooting: Temporarily set to `debug` to diagnose problems
- **Example**: `warning`
- **Note**: Similar to Serilog's minimum level filtering in .NET
- **Impact**: Affects both console output and Seq structured logging
**`ENABLE_HTTPS_SECURITY`**
- **Purpose**: Enable/disable HTTPS security headers (Helmet middleware)

View File

@ -1,4 +1,4 @@
import { Config } from '../types/index.js';
import { Config, LogLevel, LOG_LEVELS } from '../types/index.js';
import path from 'path';
import { config as dotenvConfig } from 'dotenv';
import { fileURLToPath } from 'url';
@ -10,12 +10,22 @@ const __dirname = dirname(__filename);
// Load .env file from root directory
dotenvConfig({ path: path.join(__dirname, '../../../.env') });
// Parse log level from environment variable
const parseLogLevel = (level?: string): LogLevel => {
const normalized = (level || LOG_LEVELS.INFO).toLowerCase();
const validLevels = Object.values(LOG_LEVELS);
return validLevels.includes(normalized as LogLevel)
? (normalized as LogLevel)
: LOG_LEVELS.INFO;
};
export const config: Config = {
port: parseInt(process.env.PORT || '5172', 10),
basePath: process.env.BASE_PATH || '/',
maxmindDbPath: process.env.MAXMIND_DB_PATH || '/usr/share/maxmind',
seqUrl: process.env.SEQ_URL,
seqApiKey: process.env.SEQ_API_KEY,
logLevel: parseLogLevel(process.env.LOG_LEVEL),
enableHttpsSecurity: process.env.ENABLE_HTTPS_SECURITY === 'true',
apiKeys: {
frontend: process.env.FRONTEND_API_KEY || 'frontend-default-key',

View File

@ -96,7 +96,7 @@ class GeoIPService {
const response: City = this.cityReader!.city(ip);
const result: DetailedGeoIPResponse = {
ip,
location: response as any as GeoIPLocation,
location: response as GeoIPLocation,
};
this.cache.set(cacheKey, result);

View File

@ -1,10 +1,14 @@
import { Logger as SeqLogger, SeqLoggerConfig } from 'seq-logging';
import { LogLevel, LOG_LEVELS, LOG_LEVEL_PRIORITY } from '../types/index.js';
import config from './config.js';
class Logger {
private seqLogger?: SeqLogger;
private minLevel: LogLevel;
constructor() {
this.minLevel = config.logLevel;
if (config.seqUrl) {
const seqConfig: SeqLoggerConfig = {
serverUrl: config.seqUrl,
@ -15,9 +19,20 @@ class Logger {
};
this.seqLogger = new SeqLogger(seqConfig);
}
// Log initial configuration
console.log(
`[INFO] Logger initialized with minimum level: ${this.minLevel.toUpperCase()}`
);
}
info(message: string, properties?: any): void {
private shouldLog(level: LogLevel): boolean {
return LOG_LEVEL_PRIORITY[level] >= LOG_LEVEL_PRIORITY[this.minLevel];
}
info(message: string, properties?: Record<string, unknown>): void {
if (!this.shouldLog(LOG_LEVELS.INFO)) return;
console.log(`[INFO] ${message}`, properties || '');
if (this.seqLogger) {
this.seqLogger.emit({
@ -29,7 +44,9 @@ class Logger {
}
}
warn(message: string, properties?: any): void {
warn(message: string, properties?: Record<string, unknown>): void {
if (!this.shouldLog(LOG_LEVELS.WARNING)) return;
console.warn(`[WARN] ${message}`, properties || '');
if (this.seqLogger) {
this.seqLogger.emit({
@ -41,7 +58,13 @@ class Logger {
}
}
error(message: string, error?: Error, properties?: any): void {
error(
message: string,
error?: Error,
properties?: Record<string, unknown>
): void {
if (!this.shouldLog(LOG_LEVELS.ERROR)) return;
console.error(`[ERROR] ${message}`, error || '', properties || '');
if (this.seqLogger) {
this.seqLogger.emit({
@ -57,7 +80,9 @@ class Logger {
}
}
debug(message: string, properties?: any): void {
debug(message: string, properties?: Record<string, unknown>): void {
if (!this.shouldLog(LOG_LEVELS.DEBUG)) return;
console.debug(`[DEBUG] ${message}`, properties || '');
if (this.seqLogger) {
this.seqLogger.emit({

View File

@ -1,3 +1,7 @@
import { LogLevel } from './log';
export * from './log';
export interface GeoIPLocation {
country?: {
iso_code?: string;
@ -69,6 +73,7 @@ export interface Config {
maxmindDbPath: string;
seqUrl?: string;
seqApiKey?: string;
logLevel: LogLevel;
enableHttpsSecurity: boolean;
apiKeys: {
frontend: string;

15
src/backend/types/log.ts Normal file
View File

@ -0,0 +1,15 @@
export const LOG_LEVELS = {
DEBUG: 'debug',
INFO: 'info',
WARNING: 'warning',
ERROR: 'error',
} as const;
export type LogLevel = (typeof LOG_LEVELS)[keyof typeof LOG_LEVELS];
export const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
[LOG_LEVELS.DEBUG]: 0,
[LOG_LEVELS.INFO]: 1,
[LOG_LEVELS.WARNING]: 2,
[LOG_LEVELS.ERROR]: 3,
} as const;