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=/ BASE_PATH=/
NODE_ENV=development NODE_ENV=development
ENABLE_HTTPS_SECURITY=false ENABLE_HTTPS_SECURITY=false
LOG_LEVEL=debug
# Database Path - Local MaxMind databases location # Database Path - Local MaxMind databases location
MAXMIND_DB_PATH=D:\\tools\\maxmind-dbs MAXMIND_DB_PATH=D:\\tools\\maxmind-dbs

View File

@ -15,6 +15,14 @@ PORT=5172
BASE_PATH=/ BASE_PATH=/
NODE_ENV=production 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) # Database Path (usually doesn't need to change)
MAXMIND_DB_PATH=/usr/share/maxmind 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)", "**HTTPS Security Headers** - Toggle security headers (CSP, HSTS, COOP, CORP) based on deployment type (HTTP vs HTTPS)",
"**Port Configuration** - Customize backend and frontend ports", "**Port Configuration** - Customize backend and frontend ports",
"**Base Path Support** - Deploy under custom URL paths for reverse proxy scenarios", "**Base Path Support** - Deploy under custom URL paths for reverse proxy scenarios",
"**Logging Levels** - Adjust verbosity from debug to production", "**Configurable Logging Levels** - Hierarchical log filtering (debug, info, warning, error) similar to Serilog in .NET",
"**Seq Integration** - Optional structured logging to Seq server", "**Seq Integration** - Optional structured logging to Seq server with configurable minimum level",
"**Database Path** - Configurable GeoLite2 database location" "**Database Path** - Configurable GeoLite2 database location"
] ]
}, },
@ -217,12 +217,14 @@
"title": "Monitoring & Observability", "title": "Monitoring & Observability",
"items": [ "items": [
"**Structured Logging** - JSON-formatted logs with context and correlation IDs", "**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", "**Version Endpoint** - Check deployed version, build date, and git revision",
"**Health Checks** - Monitor service availability and database status", "**Health Checks** - Monitor service availability and database status",
"**Error Tracking** - Detailed error messages with stack traces in development", "**Error Tracking** - Detailed error messages with stack traces in development",
"**Request Logging** - Log all API requests with IP, method, path, and response time", "**Request Logging** - Log all API requests with IP, method, path, and response time",
"**Rate Limit Metrics** - Track rate limit hits and blocked requests", "**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=/ BASE_PATH=/
NODE_ENV=development NODE_ENV=development
ENABLE_HTTPS_SECURITY=false ENABLE_HTTPS_SECURITY=false
LOG_LEVEL=debug # debug | info | warning | error
# Database Path - Point to your MaxMind databases location # Database Path - Point to your MaxMind databases location
MAXMIND_DB_PATH=D:\tools\maxmind-dbs # Windows MAXMIND_DB_PATH=D:\tools\maxmind-dbs # Windows
@ -98,13 +99,14 @@ This will start:
#### 1. Configure Environment #### 1. Configure Environment
Edit `.env` file with your MaxMind credentials: Edit `.env` file with your MaxMind credentials and logging level:
```env ```env
GEOIPUPDATE_ACCOUNT_ID=your_account_id_here GEOIPUPDATE_ACCOUNT_ID=your_account_id_here
GEOIPUPDATE_LICENSE_KEY=your_license_key_here GEOIPUPDATE_LICENSE_KEY=your_license_key_here
FRONTEND_API_KEY=your-secure-frontend-api-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 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 #### 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 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 #### 3. Access the Service
- **Web Interface**: http://localhost:5172 - **Web Interface**: http://localhost:5172
@ -324,7 +348,7 @@ npm run install:all # Install all dependencies (root + backend + frontend)
### Environment Variables ### Environment Variables
| Variable | Default | Description | | Variable | Default | Description |
| -------------------------- | ---------------------- | --------------------------------------------------------------- | | -------------------------- | ---------------------- | ----------------------------------------------------- |
| `PORT` | `5172` | Server port | | `PORT` | `5172` | Server port |
| `BASE_PATH` | `/` | Application base path | | `BASE_PATH` | `/` | Application base path |
| `NODE_ENV` | `development` | Environment mode (development/production) | | `NODE_ENV` | `development` | Environment mode (development/production) |

View File

@ -120,6 +120,25 @@ ENABLE_HTTPS_SECURITY=false
- `production` - Optimized for performance, minimal logging - `production` - Optimized for performance, minimal logging
- **Example**: `production` - **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`** **`ENABLE_HTTPS_SECURITY`**
- **Purpose**: Enable/disable HTTPS security headers (Helmet middleware) - **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 path from 'path';
import { config as dotenvConfig } from 'dotenv'; import { config as dotenvConfig } from 'dotenv';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
@ -10,12 +10,22 @@ const __dirname = dirname(__filename);
// Load .env file from root directory // Load .env file from root directory
dotenvConfig({ path: path.join(__dirname, '../../../.env') }); 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 = { export const config: Config = {
port: parseInt(process.env.PORT || '5172', 10), port: parseInt(process.env.PORT || '5172', 10),
basePath: process.env.BASE_PATH || '/', basePath: process.env.BASE_PATH || '/',
maxmindDbPath: process.env.MAXMIND_DB_PATH || '/usr/share/maxmind', maxmindDbPath: process.env.MAXMIND_DB_PATH || '/usr/share/maxmind',
seqUrl: process.env.SEQ_URL, seqUrl: process.env.SEQ_URL,
seqApiKey: process.env.SEQ_API_KEY, seqApiKey: process.env.SEQ_API_KEY,
logLevel: parseLogLevel(process.env.LOG_LEVEL),
enableHttpsSecurity: process.env.ENABLE_HTTPS_SECURITY === 'true', enableHttpsSecurity: process.env.ENABLE_HTTPS_SECURITY === 'true',
apiKeys: { apiKeys: {
frontend: process.env.FRONTEND_API_KEY || 'frontend-default-key', 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 response: City = this.cityReader!.city(ip);
const result: DetailedGeoIPResponse = { const result: DetailedGeoIPResponse = {
ip, ip,
location: response as any as GeoIPLocation, location: response as GeoIPLocation,
}; };
this.cache.set(cacheKey, result); this.cache.set(cacheKey, result);

View File

@ -1,10 +1,14 @@
import { Logger as SeqLogger, SeqLoggerConfig } from 'seq-logging'; import { Logger as SeqLogger, SeqLoggerConfig } from 'seq-logging';
import { LogLevel, LOG_LEVELS, LOG_LEVEL_PRIORITY } from '../types/index.js';
import config from './config.js'; import config from './config.js';
class Logger { class Logger {
private seqLogger?: SeqLogger; private seqLogger?: SeqLogger;
private minLevel: LogLevel;
constructor() { constructor() {
this.minLevel = config.logLevel;
if (config.seqUrl) { if (config.seqUrl) {
const seqConfig: SeqLoggerConfig = { const seqConfig: SeqLoggerConfig = {
serverUrl: config.seqUrl, serverUrl: config.seqUrl,
@ -15,9 +19,20 @@ class Logger {
}; };
this.seqLogger = new SeqLogger(seqConfig); 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 || ''); console.log(`[INFO] ${message}`, properties || '');
if (this.seqLogger) { if (this.seqLogger) {
this.seqLogger.emit({ 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 || ''); console.warn(`[WARN] ${message}`, properties || '');
if (this.seqLogger) { if (this.seqLogger) {
this.seqLogger.emit({ 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 || ''); console.error(`[ERROR] ${message}`, error || '', properties || '');
if (this.seqLogger) { if (this.seqLogger) {
this.seqLogger.emit({ 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 || ''); console.debug(`[DEBUG] ${message}`, properties || '');
if (this.seqLogger) { if (this.seqLogger) {
this.seqLogger.emit({ this.seqLogger.emit({

View File

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