fixed cut overlays and solved the docker duplication thing
This commit is contained in:
parent
f4327c3c40
commit
0d3a273e22
@ -1,7 +1,7 @@
|
|||||||
FROM node:18-alpine
|
FROM node:18-alpine
|
||||||
|
|
||||||
# Install wget and dumb-init for proper signal handling
|
# Install wget for health checks
|
||||||
RUN apk add --no-cache wget dumb-init
|
RUN apk add --no-cache wget
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@ -33,6 +33,5 @@ USER nodejs
|
|||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
# Use dumb-init to handle signals properly and prevent zombie processes
|
# Use exec form to ensure PID 1 is node process
|
||||||
ENTRYPOINT ["dumb-init", "--"]
|
|
||||||
CMD ["node", "server.js"]
|
CMD ["node", "server.js"]
|
||||||
@ -5,7 +5,6 @@
|
|||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"dev": "nodemon server.js",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@ -54,8 +54,8 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ensure circle markers are visible */
|
/* Ensure circle markers are visible - but NOT cut polygons */
|
||||||
path.leaflet-interactive {
|
path.leaflet-interactive:not(.cut-polygon) {
|
||||||
stroke: #fff;
|
stroke: #fff;
|
||||||
stroke-opacity: 1;
|
stroke-opacity: 1;
|
||||||
stroke-width: 2;
|
stroke-width: 2;
|
||||||
@ -107,7 +107,15 @@ path.leaflet-interactive {
|
|||||||
/* Cut polygons - allow dynamic opacity (higher specificity to override) */
|
/* Cut polygons - allow dynamic opacity (higher specificity to override) */
|
||||||
.leaflet-container path.leaflet-interactive.cut-polygon {
|
.leaflet-container path.leaflet-interactive.cut-polygon {
|
||||||
stroke-width: 2px !important;
|
stroke-width: 2px !important;
|
||||||
/* Allow JavaScript to control fill-opacity - remove !important */
|
/* Allow JavaScript to control fill-opacity - explicitly do NOT override it */
|
||||||
|
stroke: currentColor !important;
|
||||||
|
stroke-opacity: 0.8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Additional specificity rule for cut polygons to ensure JS opacity takes precedence */
|
||||||
|
path.leaflet-interactive.cut-polygon {
|
||||||
|
/* Do not set fill-opacity here - let JavaScript control it */
|
||||||
|
stroke-width: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Marker being moved */
|
/* Marker being moved */
|
||||||
|
|||||||
@ -37,6 +37,10 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(255, 68, 68, 0.3);
|
background: rgba(255, 68, 68, 0.3);
|
||||||
animation: pulse-ring 2s ease-out infinite;
|
animation: pulse-ring 2s ease-out infinite;
|
||||||
|
/* Position the pulse at the tip of the marker */
|
||||||
|
top: 24px;
|
||||||
|
left: 0;
|
||||||
|
transform-origin: center center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes bounce-marker {
|
@keyframes bounce-marker {
|
||||||
|
|||||||
@ -184,13 +184,9 @@ export class CutManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// ...existing code...
|
||||||
* Display a cut on the map (enhanced to support multiple cuts)
|
|
||||||
*/
|
displayCut(cutData, autoDisplayed = false) {
|
||||||
/**
|
|
||||||
* Display a cut on the map (enhanced to support multiple cuts)
|
|
||||||
*/
|
|
||||||
displayCut(cutData, autoDisplayed = false) {
|
|
||||||
if (!this.map) {
|
if (!this.map) {
|
||||||
console.error('Map not initialized');
|
console.error('Map not initialized');
|
||||||
return false;
|
return false;
|
||||||
@ -226,14 +222,41 @@ export class CutManager {
|
|||||||
const geojsonData = typeof normalizedCut.geojson === 'string' ?
|
const geojsonData = typeof normalizedCut.geojson === 'string' ?
|
||||||
JSON.parse(normalizedCut.geojson) : normalizedCut.geojson;
|
JSON.parse(normalizedCut.geojson) : normalizedCut.geojson;
|
||||||
|
|
||||||
|
// Parse opacity value - ensure it's a number between 0 and 1
|
||||||
|
let opacityValue = parseFloat(normalizedCut.opacity);
|
||||||
|
|
||||||
|
// Validate opacity is within range
|
||||||
|
if (isNaN(opacityValue) || opacityValue < 0 || opacityValue > 1) {
|
||||||
|
opacityValue = 0.3; // Default fallback
|
||||||
|
console.log(`Invalid opacity value (${normalizedCut.opacity}), using default: ${opacityValue}`);
|
||||||
|
}
|
||||||
|
|
||||||
const cutLayer = L.geoJSON(geojsonData, {
|
const cutLayer = L.geoJSON(geojsonData, {
|
||||||
style: {
|
style: {
|
||||||
color: normalizedCut.color || '#3388ff',
|
color: normalizedCut.color || '#3388ff',
|
||||||
fillColor: normalizedCut.color || '#3388ff',
|
fillColor: normalizedCut.color || '#3388ff',
|
||||||
fillOpacity: parseFloat(normalizedCut.opacity) || 0.3,
|
fillOpacity: opacityValue,
|
||||||
weight: 2,
|
weight: 2,
|
||||||
opacity: 1,
|
opacity: 0.8, // Stroke opacity - keeping this slightly transparent for better visibility
|
||||||
className: 'cut-polygon'
|
className: 'cut-polygon'
|
||||||
|
},
|
||||||
|
// Add onEachFeature to apply styles to each individual feature
|
||||||
|
onEachFeature: function (feature, layer) {
|
||||||
|
// Apply styles directly to the layer to ensure they override CSS
|
||||||
|
if (layer.setStyle) {
|
||||||
|
layer.setStyle({
|
||||||
|
fillOpacity: opacityValue,
|
||||||
|
color: normalizedCut.color || '#3388ff',
|
||||||
|
fillColor: normalizedCut.color || '#3388ff',
|
||||||
|
weight: 2,
|
||||||
|
opacity: 0.8
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add cut-polygon class to the path element
|
||||||
|
if (layer._path) {
|
||||||
|
layer._path.classList.add('cut-polygon');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -249,6 +272,15 @@ export class CutManager {
|
|||||||
|
|
||||||
cutLayer.addTo(this.map);
|
cutLayer.addTo(this.map);
|
||||||
|
|
||||||
|
// Ensure cut-polygon class is applied to all path elements after adding to map
|
||||||
|
cutLayer.eachLayer(function(layer) {
|
||||||
|
if (layer._path) {
|
||||||
|
layer._path.classList.add('cut-polygon');
|
||||||
|
// Force the fill-opacity style to ensure it overrides CSS
|
||||||
|
layer._path.style.fillOpacity = opacityValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Store in both tracking systems
|
// Store in both tracking systems
|
||||||
this.cutLayers.set(normalizedCut.id, cutLayer);
|
this.cutLayers.set(normalizedCut.id, cutLayer);
|
||||||
this.displayedCuts.set(normalizedCut.id, normalizedCut);
|
this.displayedCuts.set(normalizedCut.id, normalizedCut);
|
||||||
@ -257,18 +289,17 @@ export class CutManager {
|
|||||||
this.currentCut = normalizedCut;
|
this.currentCut = normalizedCut;
|
||||||
this.currentCutLayer = cutLayer;
|
this.currentCutLayer = cutLayer;
|
||||||
|
|
||||||
console.log(`Displayed cut: ${normalizedCut.name} (ID: ${normalizedCut.id})`);
|
console.log(`Displayed cut: ${normalizedCut.name} (ID: ${normalizedCut.id}) with opacity: ${opacityValue} (raw: ${normalizedCut.opacity})`);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error displaying cut:', error);
|
console.error('Error displaying cut:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ...existing code...
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide the currently displayed cut (legacy method - now hides all cuts)
|
|
||||||
*/
|
|
||||||
hideCut() {
|
hideCut() {
|
||||||
this.hideAllCuts();
|
this.hideAllCuts();
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,16 @@
|
|||||||
|
// At the very top of the file, before any requires
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// Use a more robust check for duplicate execution
|
||||||
|
if (global.__serverInitialized) {
|
||||||
|
console.log(`[INIT] Server already initialized - EXITING`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
global.__serverInitialized = true;
|
||||||
|
|
||||||
// Prevent duplicate execution
|
// Prevent duplicate execution
|
||||||
if (require.main !== module) {
|
if (require.main !== module) {
|
||||||
console.log('Server.js being imported, not executed directly');
|
console.log('[INIT] Server.js being imported, not executed directly - EXITING');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -12,10 +22,6 @@ const cookieParser = require('cookie-parser');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
// Debug: Check if server.js is being loaded multiple times
|
|
||||||
const serverInstanceId = Math.random().toString(36).substr(2, 9);
|
|
||||||
console.log(`[DEBUG] Server.js PID:${process.pid} instance ${serverInstanceId} loading at ${new Date().toISOString()}`);
|
|
||||||
|
|
||||||
// Import configuration and utilities
|
// Import configuration and utilities
|
||||||
const config = require('./config');
|
const config = require('./config');
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
@ -24,8 +30,14 @@ const { apiLimiter } = require('./middleware/rateLimiter');
|
|||||||
const { cacheBusting } = require('./utils/cacheBusting');
|
const { cacheBusting } = require('./utils/cacheBusting');
|
||||||
const { initializeEmailService } = require('./services/email');
|
const { initializeEmailService } = require('./services/email');
|
||||||
|
|
||||||
// Initialize Express app
|
// Initialize Express app - only create once
|
||||||
|
if (global.__expressApp) {
|
||||||
|
console.log('[INIT] Express app already created - EXITING');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
global.__expressApp = app;
|
||||||
|
|
||||||
// Trust proxy for Cloudflare
|
// Trust proxy for Cloudflare
|
||||||
app.set('trust proxy', true);
|
app.set('trust proxy', true);
|
||||||
@ -33,8 +45,8 @@ app.set('trust proxy', true);
|
|||||||
// Cookie parser
|
// Cookie parser
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
|
|
||||||
// Session configuration
|
// Session configuration - only initialize once
|
||||||
app.use(session({
|
const sessionMiddleware = session({
|
||||||
secret: config.session.secret,
|
secret: config.session.secret,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
@ -44,36 +56,34 @@ app.use(session({
|
|||||||
// Use a custom session ID generator to avoid conflicts
|
// Use a custom session ID generator to avoid conflicts
|
||||||
return crypto.randomBytes(16).toString('hex');
|
return crypto.randomBytes(16).toString('hex');
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
|
||||||
|
app.use(sessionMiddleware);
|
||||||
|
|
||||||
// Build dynamic CSP configuration
|
// Build dynamic CSP configuration
|
||||||
const buildConnectSrc = () => {
|
const buildConnectSrc = () => {
|
||||||
const sources = ["'self'"];
|
const sources = ["'self'"];
|
||||||
|
|
||||||
// Add MkDocs URLs from config
|
// Add NocoDB API URL
|
||||||
if (config.mkdocs?.url) {
|
if (config.nocodb.apiUrl) {
|
||||||
sources.push(config.mkdocs.url);
|
try {
|
||||||
|
const nocodbUrl = new URL(config.nocodb.apiUrl);
|
||||||
|
sources.push(`${nocodbUrl.protocol}//${nocodbUrl.host}`);
|
||||||
|
} catch (e) {
|
||||||
|
// Invalid URL, skip
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add localhost ports from environment
|
// Add Edmonton Open Data Portal
|
||||||
const mkdocsPort = process.env.MKDOCS_PORT || '4000';
|
|
||||||
const mkdocsSitePort = process.env.MKDOCS_SITE_SERVER_PORT || '4002';
|
|
||||||
|
|
||||||
sources.push(`http://localhost:${mkdocsPort}`);
|
|
||||||
sources.push(`http://localhost:${mkdocsSitePort}`);
|
|
||||||
|
|
||||||
// Add City of Edmonton Socrata API
|
|
||||||
sources.push('https://data.edmonton.ca');
|
sources.push('https://data.edmonton.ca');
|
||||||
|
|
||||||
// Add Stadia Maps for better tile coverage
|
// Add Nominatim for geocoding
|
||||||
sources.push('https://tiles.stadiamaps.com');
|
sources.push('https://nominatim.openstreetmap.org');
|
||||||
|
|
||||||
// Add production domains if in production
|
// Add localhost for development
|
||||||
if (config.isProduction || process.env.NODE_ENV === 'production') {
|
if (!config.isProduction) {
|
||||||
// Add the main domain from environment
|
sources.push('http://localhost:*');
|
||||||
const mainDomain = process.env.DOMAIN || 'cmlite.org';
|
sources.push('ws://localhost:*');
|
||||||
sources.push(`https://${mainDomain}`);
|
|
||||||
sources.push('https://cmlite.org'); // Fallback
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sources;
|
return sources;
|
||||||
@ -95,15 +105,27 @@ app.use(helmet({
|
|||||||
// CORS configuration
|
// CORS configuration
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: function(origin, callback) {
|
origin: function(origin, callback) {
|
||||||
// Allow requests with no origin (like mobile apps or curl requests)
|
// Allow requests with no origin (like Postman or server-to-server)
|
||||||
if (!origin) return callback(null, true);
|
if (!origin) return callback(null, true);
|
||||||
|
|
||||||
const allowedOrigins = config.cors.allowedOrigins;
|
// In production, be more restrictive
|
||||||
if (allowedOrigins.includes(origin) || allowedOrigins.includes('*')) {
|
if (config.isProduction) {
|
||||||
|
const allowedOrigins = [
|
||||||
|
`https://${config.domain}`,
|
||||||
|
`https://map.${config.domain}`,
|
||||||
|
`https://docs.${config.domain}`,
|
||||||
|
`https://admin.${config.domain}`
|
||||||
|
];
|
||||||
|
|
||||||
|
if (allowedOrigins.includes(origin)) {
|
||||||
callback(null, true);
|
callback(null, true);
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('Not allowed by CORS'));
|
callback(new Error('Not allowed by CORS'));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// In development, allow localhost
|
||||||
|
callback(null, true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
credentials: true,
|
credentials: true,
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
@ -136,25 +158,24 @@ app.get('/api/version', (req, res) => {
|
|||||||
// Proxy endpoint for MkDocs search
|
// Proxy endpoint for MkDocs search
|
||||||
app.get('/api/docs-search', async (req, res) => {
|
app.get('/api/docs-search', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const mkdocsUrl = config.mkdocs?.url || `http://localhost:${process.env.MKDOCS_SITE_SERVER_PORT || '4002'}`;
|
const docsUrl = config.isProduction ?
|
||||||
logger.info(`Fetching search index from: ${mkdocsUrl}/search/search_index.json`);
|
`https://docs.${config.domain}/search/search_index.json` :
|
||||||
|
'http://localhost:8000/search/search_index.json';
|
||||||
const response = await fetch(`${mkdocsUrl}/search/search_index.json`);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Failed to fetch search index: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const response = await fetch(docsUrl);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
res.json(data);
|
res.json(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error fetching search index:', error);
|
logger.error('Failed to fetch docs search index:', error);
|
||||||
res.status(500).json({ error: 'Failed to fetch search index' });
|
res.status(500).json({ error: 'Failed to fetch search index' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize email service
|
// Initialize email service - only once
|
||||||
initializeEmailService();
|
if (!global.__emailInitialized) {
|
||||||
|
initializeEmailService();
|
||||||
|
global.__emailInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Import and setup routes
|
// Import and setup routes
|
||||||
require('./routes')(app);
|
require('./routes')(app);
|
||||||
@ -162,63 +183,70 @@ require('./routes')(app);
|
|||||||
// Error handling middleware
|
// Error handling middleware
|
||||||
app.use((err, req, res, next) => {
|
app.use((err, req, res, next) => {
|
||||||
logger.error('Unhandled error:', err);
|
logger.error('Unhandled error:', err);
|
||||||
|
res.status(500).json({
|
||||||
// Don't leak error details in production
|
error: 'Internal server error',
|
||||||
const message = config.isProduction ?
|
message: config.isProduction ? 'An error occurred' : err.message
|
||||||
'Internal server error' :
|
|
||||||
err.message || 'Internal server error';
|
|
||||||
|
|
||||||
res.status(err.status || 500).json({
|
|
||||||
success: false,
|
|
||||||
error: message
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start server
|
// Add a simple health check endpoint early in the middleware stack (before other middlewares)
|
||||||
const server = app.listen(config.port, () => {
|
app.get('/health', (req, res) => {
|
||||||
|
res.status(200).json({
|
||||||
|
status: 'healthy',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
uptime: process.uptime()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Only start server if not already started
|
||||||
|
if (!global.__serverStarted) {
|
||||||
|
global.__serverStarted = true;
|
||||||
|
|
||||||
|
const server = app.listen(config.port, () => {
|
||||||
logger.info(`
|
logger.info(`
|
||||||
╔════════════════════════════════════════╗
|
╔════════════════════════════════════════╗
|
||||||
║ BNKops Map Server ║
|
║ BNKops Map Server ║
|
||||||
╠════════════════════════════════════════╣
|
╠════════════════════════════════════════╣
|
||||||
║ Status: Running ║
|
║ Status: Running ║
|
||||||
║ Port: ${config.port} ║
|
║ Port: ${config.port} ║
|
||||||
║ Environment: ${config.nodeEnv} ║
|
║ Environment: ${config.isProduction ? 'production' : 'development'} ║
|
||||||
║ Project ID: ${config.nocodb.projectId} ║
|
║ Project ID: ${config.nocodb.projectId} ║
|
||||||
║ Table ID: ${config.nocodb.tableId} ║
|
║ Table ID: ${config.nocodb.tableId} ║
|
||||||
║ Login Sheet: ${config.nocodb.loginSheetId || 'Not Configured'} ║
|
║ Login Sheet: ${config.nocodb.loginSheetId} ║
|
||||||
║ PID: ${process.pid} ║
|
║ PID: ${process.pid} ║
|
||||||
║ Time: ${new Date().toISOString()} ║
|
║ Time: ${new Date().toISOString()} ║
|
||||||
╚════════════════════════════════════════╝
|
╚════════════════════════════════════════╝
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
process.on('SIGTERM', () => {
|
process.on('SIGTERM', () => {
|
||||||
logger.info('SIGTERM signal received: closing HTTP server');
|
logger.info('SIGTERM signal received: closing HTTP server');
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
logger.info('HTTP server closed');
|
logger.info('HTTP server closed');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
logger.info('SIGINT signal received: closing HTTP server');
|
logger.info('SIGINT signal received: closing HTTP server');
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
logger.info('HTTP server closed');
|
logger.info('HTTP server closed');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle uncaught exceptions
|
// Handle uncaught exceptions
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
logger.error('Uncaught exception:', err);
|
logger.error('Uncaught Exception:', err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle unhandled promise rejections
|
// Handle unhandled promise rejections
|
||||||
process.on('unhandledRejection', (reason, promise) => {
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
||||||
@ -1,20 +1,10 @@
|
|||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
const config = require('../config');
|
const config = require('../config');
|
||||||
|
|
||||||
// Debug: Check if logger is being created multiple times
|
// Create the logger only once
|
||||||
const instanceId = Math.random().toString(36).substr(2, 9);
|
|
||||||
console.log(`[DEBUG] Creating logger instance ${instanceId} at ${new Date().toISOString()}`);
|
|
||||||
|
|
||||||
// Ensure we only create one logger instance
|
|
||||||
if (global.appLogger) {
|
|
||||||
console.log(`[DEBUG] Reusing existing logger instance`);
|
|
||||||
module.exports = global.appLogger;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const logger = winston.createLogger({
|
const logger = winston.createLogger({
|
||||||
level: config.isProduction ? 'info' : 'debug',
|
level: config.isProduction ? 'info' : 'debug',
|
||||||
defaultMeta: { service: 'bnkops-map', instanceId },
|
defaultMeta: { service: 'bnkops-map' },
|
||||||
transports: [
|
transports: [
|
||||||
new winston.transports.Console({
|
new winston.transports.Console({
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(
|
||||||
@ -50,7 +40,4 @@ if (config.isProduction) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store logger globally to prevent multiple instances
|
|
||||||
global.appLogger = logger;
|
|
||||||
|
|
||||||
module.exports = logger;
|
module.exports = logger;
|
||||||
Loading…
x
Reference in New Issue
Block a user