Logging
The eTeamups Platform implements a multi-layer logging strategy that captures application events, API requests, and reverse proxy traffic. This guide covers all logging components, their configuration, and tools for analysis.
Logging Architecture Overview
The platform has three distinct logging layers:
| Layer | Technology | Storage | Purpose |
|---|---|---|---|
| Application | Winston + OpenTelemetry | File system + SigNoz | Structured application logs with trace correlation |
| API Requests | BasicMiddleware | MongoDB apilogs collection |
Detailed request/response metadata for every API call |
| Reverse Proxy | Nginx | File system (/var/log/nginx/) |
Access logs, error logs, security events, per-service traffic |
Application Logging (Winston)
Configuration
The application logger is defined in libs/utility/src/Logger.ts and is shared across all microservices. It uses the Winston logging library with three transports:
- Console transport – Colorized, human-readable output for development and container stdout.
- File transport (error.log) – Captures only
error-level messages in JSON format. - File transport (combined.log) – Captures all log levels in JSON format.
- OpenTelemetry transport – Sends log records to SigNoz via the
@opentelemetry/winston-transportpackage.
Log Format
All file-based logs use structured JSON with the following fields:
{
"level": "info",
"message": "Server running on Port 9000",
"timestamp": "2024-11-27 14:30:00",
"service": "zeswa-platform"
}
The service field defaults to the value of the OTEL_SERVICE_NAME environment variable (or eteamups-platform-meta if unset).
Error logs include a stack field with the full stack trace.
Log Level
The log level is controlled by the LOG_LEVEL environment variable (docker.env). Valid levels in order of severity:
error > warn > info > http > verbose > debug > silly
The default is info.
Logger API
The Logger class provides static methods that can be imported from libs/utility/src/Logger.ts:
import { Logger } from "libs/utility/src/Logger";
Logger.info("User registered successfully", { userId: "abc123" });
Logger.warn("Rate limit approaching", { ip: "192.168.1.1" });
Logger.error(new Error("Database connection failed"));
Logger.debug("Query result", { count: 42 });
Trace Correlation
When OpenTelemetry instrumentation is active, every Winston log record automatically includes trace_id and span_id fields. This allows you to:
- Search for a log message in SigNoz.
- Click through to the associated distributed trace.
- See the exact request context that generated the log.
API Request Logging (MongoDB)
How It Works
The BasicMiddleware class in services/common/src/BasicMiddleware.ts intercepts every incoming HTTP request and logs it to MongoDB after the response completes.
The middleware flow:
- Pre-hook (
pre()) – Records the start time when a request arrives. - Request capture (
on()) – Extracts request metadata (URL, method, headers, body, query, params, remote address). - Post-hook (
post()) – Fires on the responsecloseevent. Calculates processing time and writes the log entry to MongoDB.
Logged Fields
Each API log entry in the apilogs collection contains:
| Field | Description |
|---|---|
rawHeaders |
Full list of raw HTTP request headers |
url |
Request URL path |
method |
HTTP method (GET, POST, PUT, DELETE, etc.) |
httpVersion |
HTTP protocol version |
remoteAddress |
Client IP address |
remoteFamily |
IP address family (IPv4/IPv6) |
startTime |
Timestamp when the request was received |
endTime |
Timestamp when the response was sent |
processingTime |
Duration in milliseconds |
body |
Request body (for POST/PUT requests) |
params |
URL path parameters |
query |
URL query string parameters |
callerApp |
Identified calling application: admin, hub, postman, or unknown |
Caller Identification
Every request is tagged with a callerApp value identifying the source application. Detection priority:
x-app-sourceheader (most reliable) — client sendsadminorhubexplicitly- User-Agent — requests from Postman (
PostmanRuntime/...) are auto-tagged aspostman - Origin / Referer — matched against
ADMIN_URLandHUB_URLenv vars - Fallback —
unknown
Sending x-app-source from client apps
Admin portal — add to every request:
// Centralised fetch wrapper
export const apiClient = (path: string, options: RequestInit = {}) =>
fetch(`${process.env.NEXT_PUBLIC_API_URL}${path}`, {
...options,
headers: { 'x-app-source': 'admin', ...(options.headers ?? {}) },
});
// Or Axios default
api.defaults.headers.common['x-app-source'] = 'admin';
Hub — same pattern with value hub:
headers: { 'x-app-source': 'hub', 'Authorization': `Bearer ${token}` }
Postman — no action needed; auto-detected from User-Agent.
Querying API Logs
Connect to MongoDB and query the apilogs collection:
// Find slow requests (> 1 second)
db.apilogs.find({ processingTime: { $gt: 1000 } }).sort({ startTime: -1 });
// Find all POST requests to auth endpoints
db.apilogs.find({ method: "POST", url: { $regex: /^\/auth/ } });
// Count requests by method in the last 24 hours
db.apilogs.aggregate([
{ $match: { startTime: { $gte: new Date(Date.now() - 86400000) } } },
{ $group: { _id: "$method", count: { $sum: 1 } } }
]);
// Find requests from a specific IP
db.apilogs.find({ remoteAddress: "192.168.1.100" }).sort({ startTime: -1 });
// Average processing time by URL
db.apilogs.aggregate([
{ $group: { _id: "$url", avgTime: { $avg: "$processingTime" }, count: { $sum: 1 } } },
{ $sort: { avgTime: -1 } },
{ $limit: 20 }
]);
// All requests from Admin portal
db.apilogs.find({ callerApp: "admin" }).sort({ startTime: -1 });
// All requests from Hub
db.apilogs.find({ callerApp: "hub" }).sort({ startTime: -1 });
// Breakdown by caller
db.apilogs.aggregate([
{ $group: { _id: "$callerApp", count: { $sum: 1 } } },
{ $sort: { count: -1 } }
]);
// Slow admin requests (> 500ms)
db.apilogs.find({ callerApp: "admin", processingTime: { $gt: 500 } })
.sort({ processingTime: -1 });
Nginx Logging
Nginx provides the outermost logging layer, capturing all traffic that reaches the reverse proxy.
Log Formats
Four log formats are defined in nginx/nginx.conf:
1. Standard Format (main)
Basic Apache-style combined log format. Compatible with most log analysis tools.
$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent
"$http_referer" "$http_user_agent" "$http_x_forwarded_for"
2. Detailed Format (detailed)
Enhanced format with upstream timing information. This is the default format for access logs.
$remote_addr - $remote_user [$time_local] "$request"
$status $body_bytes_sent "$http_referer" "$http_user_agent"
"$http_x_forwarded_for" rt=$request_time uct="$upstream_connect_time"
uht="$upstream_header_time" urt="$upstream_response_time"
cs=$upstream_cache_status host="$host" proto="$scheme"
Key timing fields:
| Field | Description |
|---|---|
rt |
Total request time from Nginx’s perspective |
uct |
Time to establish connection with upstream |
uht |
Time until upstream sent response headers |
urt |
Total upstream response time |
3. JSON Format (json_combined)
Structured JSON output suitable for ingestion by log aggregation tools (ELK, Loki, etc.).
{
"time_local": "27/Nov/2024:14:30:00 +0000",
"remote_addr": "192.168.1.1",
"request": "GET /auth/health HTTP/1.1",
"status": "200",
"request_time": "0.003",
"upstream_response_time": "0.002"
}
4. Security Format (security)
Includes the request body for security audit purposes. Used for authentication endpoints and suspicious request logging.
$remote_addr - $remote_user [$time_local] "$request" $status
"$http_user_agent" "$http_referer" "$http_x_forwarded_for" "$request_body"
Log Files
Nginx writes to multiple log files, organized by purpose and service:
General Logs
| File | Format | Description |
|---|---|---|
access.log |
detailed | Main access log for all requests (buffered: 64k, flush: 1s) |
access.json.log |
json_combined | Structured JSON access log (buffered: 64k, flush: 1s) |
error.log |
– | Error-level messages |
debug.log |
– | Debug-level messages (verbose, for troubleshooting only) |
redirect.log |
security | HTTP-to-HTTPS redirect tracking |
Per-Service Logs
| File | Format | Description |
|---|---|---|
api-gateway.log |
detailed | Requests to the API gateway root |
auth-service.log |
detailed | All requests routed to auth-service |
auth-security.log |
security | Auth requests with request body for security auditing |
profile-service.log |
detailed | All requests routed to profile-service |
organisation-service.log |
detailed | All requests routed to organisation-service |
media-service.log |
detailed | All requests routed to media-service |
media-uploads.log |
detailed | File upload traffic tracking |
static-files.log |
detailed | Static asset requests |
Per-Virtual-Host Logs
| File | Format | Description |
|---|---|---|
hub-access.log |
detailed | Traffic to www.zeswa.com |
hub-access.json.log |
json_combined | Same in JSON format |
hub-error.log |
– | Hub errors |
admin-access.log |
detailed | Traffic to admin.zeswa.com |
admin-access.json.log |
json_combined | Same in JSON format |
admin-error.log |
– | Admin portal errors |
api-access.log |
detailed | Traffic to api.zeswa.com |
api-access.json.log |
json_combined | Same in JSON format |
api-error.log |
– | API errors |
Security and Error Logs
| File | Format | Description |
|---|---|---|
security-alerts.log |
security | Suspicious requests (.php, .asp, .aspx, .jsp extensions, hidden file access) |
bot-requests.log |
security | Requests from detected bots/crawlers |
404-errors.log |
detailed | All not-found errors |
server-errors.log |
detailed | All 5xx server errors |
Rate Limiting
Nginx defines two rate-limiting zones:
| Zone | Rate | Scope |
|---|---|---|
login |
5 requests/minute per IP | Auth service endpoints (/auth) |
api |
10 requests/second per IP | Profile, organisation, and media endpoints |
Both zones allow burst traffic with the nodelay directive. Excess requests receive HTTP 429 Too Many Requests.
Bot Detection
Nginx uses a map directive to detect bots based on the User-Agent header. Requests matching patterns like bot, crawler, spider, scanner, curl, or wget are flagged and logged separately to bot-requests.log.
Log Rotation
Automated Rotation
Log rotation runs daily at midnight via a cron job. Set it up with:
./scripts/setup-log-rotation.sh
This adds the following cron entry:
0 0 * * * cd /path/to/project && ./scripts/rotate-logs.sh >> logs/rotation.log 2>&1
Manual Rotation
Trigger rotation manually at any time:
./scripts/rotate-logs.sh
Rotation Process
The scripts/rotate-logs.sh script performs the following steps:
- Copy logs from the Nginx container – Logs are copied from
/var/log/nginx/inside the container to the locallogs/directory. - Rotate each log file – Non-empty log files are moved to a dated backup (e.g.,
access-2024-11-27-000000.log). - Compress rotated logs – Backed-up files are compressed with gzip (
.log.gz). - Signal Nginx – Sends
nginx -s reopento the container so Nginx starts writing to fresh log files. - Rotate application logs – PM2 logs (if present) and any other logs in the
logs/directory are also rotated. - Clean up old logs – Compressed logs older than the retention period are deleted.
- Report statistics – Outputs backup directory size, archive count, and oldest/newest archived log.
Rotation Configuration
| Parameter | Default | Location |
|---|---|---|
| Retention period | 30 days | BACKUP_RETENTION_DAYS in scripts/rotate-logs.sh |
| Backup directory | logs/backup/nginx/ and logs/backup/application/ |
scripts/rotate-logs.sh |
| Compression | gzip | Hardcoded in rotation script |
| Naming convention | {name}-YYYY-MM-DD-HHMMSS.log.gz |
Hardcoded in rotation script |
Directory Structure After Rotation
logs/
backup/
nginx/
access-2024-11-27-000000.log.gz
auth-service-2024-11-27-000000.log.gz
error-2024-11-27-000000.log.gz
...
application/
app-combined-2024-11-27-000000.log.gz
app-error-2024-11-27-000000.log.gz
...
rotation.log # Log rotation execution log
Log Analysis with log-monitor.sh
The scripts/log-monitor.sh script provides a comprehensive CLI tool for analyzing Nginx logs.
Usage
./scripts/log-monitor.sh [OPTION]
Running without options shows statistics and recent errors.
Available Flags
| Flag | Long Form | Description |
|---|---|---|
-r |
--realtime |
Real-time log monitoring with color-coded output (errors in red, warnings in yellow, auth events in purple, mutations in cyan) |
-s |
--stats |
Comprehensive statistics: total requests, unique IPs, top IPs, top endpoints, HTTP status code distribution |
-e |
--errors |
Recent entries from error.log, server-errors.log, and 404-errors.log |
-a |
--auth |
Authentication analysis: login attempts (successful and failed), top IPs attempting auth |
-p |
--performance |
Response time analysis by endpoint (requires detailed log format), request rate per hour |
-u |
--uploads |
Media upload statistics with file size distribution |
-t |
--threats |
Security threat analysis: top attacking IPs, common attack patterns |
-b |
--bots |
Bot and crawler activity: total bot requests, top bot user agents |
-c |
--cleanup |
Disk usage analysis: backup size, file count, age, cleanup suggestions for logs older than 30 days |
-h |
--help |
Display usage information |
Examples
# Monitor logs in real time
./scripts/log-monitor.sh -r
# Check for recent errors
./scripts/log-monitor.sh -e
# Analyze authentication patterns
./scripts/log-monitor.sh -a
# Review performance metrics
./scripts/log-monitor.sh -p
# Check for security threats
./scripts/log-monitor.sh -t
# See disk usage and cleanup suggestions
./scripts/log-monitor.sh -c
Common Log Queries
Nginx Access Log Queries
# Top 10 IP addresses by request count
awk '{print $1}' logs/access.log | sort | uniq -c | sort -nr | head -10
# HTTP status code distribution
awk '{print $9}' logs/access.log | sort | uniq -c | sort -nr
# Requests with response time > 1 second
grep "rt=" logs/access.log | awk '{match($0, /rt=([0-9.]+)/, rt); if (rt[1] > 1) print}'
# Top 10 slowest requests
grep "rt=" logs/access.log | awk '{match($0, /rt=([0-9.]+)/, rt); print rt[1], $0}' | sort -nr | head -10
# Requests per hour
awk '{gsub(/\[|\]/, "", $4); split($4, dt, ":"); hours[dt[2]]++} END {for (h in hours) printf "%02d:00 - %d requests\n", h, hours[h]}' logs/access.log | sort
Security Log Queries
# Recent security alerts
tail -50 logs/security-alerts.log
# Failed authentication attempts (401/403)
grep -E "401|403" logs/auth-security.log | tail -20
# IP addresses with most failed auth attempts
grep -E "401|403" logs/auth-security.log | awk '{print $1}' | sort | uniq -c | sort -nr | head -10
# Requests to suspicious file extensions
grep -E "\.(php|asp|aspx|jsp)" logs/security-alerts.log | wc -l
# Monitor security alerts in real time
tail -f logs/security-alerts.log
Error Log Queries
# Recent server errors
tail -20 logs/server-errors.log
# Count 404 errors by URL
awk '{print $7}' logs/404-errors.log | sort | uniq -c | sort -nr | head -10
# Recent upstream errors
grep "upstream" logs/error.log | tail -20
Security Log Monitoring
What Gets Logged
The Nginx security configuration automatically detects and logs:
- Suspicious file extensions – Requests for
.php,.asp,.aspx,.jspfiles are blocked with 403 and logged tosecurity-alerts.log. - Hidden file access – Requests matching
/.(e.g.,/.env,/.git) are denied and logged tosecurity-alerts.log. - Bot traffic – Requests from user agents containing
bot,crawler,spider,scanner,curl, orwgetare logged tobot-requests.log. - Auth events – All authentication requests are double-logged: once to
auth-service.log(detailed) and once toauth-security.log(security format with request body). - Rate limit violations – Requests exceeding rate limits receive 429 and appear in the standard access logs.
Recommended Monitoring Routine
- Daily – Run
./scripts/log-monitor.sh -tto check for new security threats. - Daily – Run
./scripts/log-monitor.sh -ato review authentication patterns and look for brute-force attempts. - Weekly – Run
./scripts/log-monitor.sh -bto review bot activity and decide whether to block persistent scanners. - On incident – Use
./scripts/log-monitor.sh -rfor real-time monitoring while investigating.
Docker Container Log Access
Docker container logs (stdout/stderr) can be accessed independently from the file-based logs:
# View logs for a specific service
docker compose logs auth-service
# Follow logs in real time
docker compose logs -f auth-service
# View logs for all services
docker compose logs -f
# View last 100 lines
docker compose logs --tail 100 auth-service
# View logs since a specific time
docker compose logs --since "2024-11-27T14:00:00" auth-service
The scripts/view-logs.sh script provides a convenience wrapper for viewing service logs.
Log Viewing Scripts
# View service logs (convenience wrapper)
./scripts/view-logs.sh
# Manual log rotation
./scripts/rotate-logs.sh
# Set up automated log rotation
./scripts/setup-log-rotation.sh