Parcourir la source

Implement Structured Logging

Benjamin Harris il y a 1 mois
Parent
commit
53f727359c

+ 1 - 1
services/bluesky/index.js

@@ -90,7 +90,7 @@ class BlueskyService extends BasePlatformService {
         );
       }
     } catch (err) {
-      console.error('[Bluesky] MongoDB write error:', err.message);
+      this.app.log.error({ action: 'feed_write', platform: 'bluesky', outcome: 'failure', err: err.message });
     }
 
     return items;

+ 1 - 1
services/facebook/index.js

@@ -103,7 +103,7 @@ class FacebookService extends BasePlatformService {
         );
       }
     } catch (err) {
-      console.error('[Facebook] MongoDB write error:', err.message);
+      this.app.log.error({ action: 'feed_write', platform: 'facebook', outcome: 'failure', err: err.message });
     }
 
     return allItems;

+ 11 - 10
services/feed-aggregator/index.js

@@ -4,10 +4,10 @@ const axios = require('axios');
 const cron = require('node-cron');
 const { getDb } = require('./utils/MongoDBConnector');
 const RabbitMQProducer = require('./utils/RabbitMQProducer');
+const { createLogger } = require('./utils/logger');
 
-const FEED_REFRESH_INTERVAL = process.env.FEED_REFRESH_INTERVAL || '*/5 * * * *'; // Her 5 dakika
+const FEED_REFRESH_INTERVAL = process.env.FEED_REFRESH_INTERVAL || '*/5 * * * *';
 
-// Platform servis URL'leri (docker network içinde)
 const PLATFORM_SERVICES = {
   twitter:   process.env.TWITTER_SERVICE_URL   || 'http://twitter:3001',
   linkedin:  process.env.LINKEDIN_SERVICE_URL  || 'http://linkedin:3002',
@@ -17,7 +17,8 @@ const PLATFORM_SERVICES = {
   facebook:  process.env.FACEBOOK_SERVICE_URL  || 'http://facebook:3006',
 };
 
-const app = Fastify({ logger: false });
+const log = createLogger('feed-aggregator');
+const app = Fastify({ logger: log });
 let producer;
 
 // ─── Feed Çekme ──────────────────────────────────────────────────────────────
@@ -26,7 +27,7 @@ async function fetchPlatformFeed(platform, serviceUrl) {
   try {
     const response = await axios.get(`${serviceUrl}/feed`, { timeout: 15000 });
     const items = response.data.items || [];
-    console.log(`[FeedAggregator] ${platform}: ${items.length} öğe çekildi`);
+    log.info({ action: 'feed_fetch', platform, count: items.length, outcome: 'success' });
 
     // WebSocket üzerinden UI'ya bildir
     if (producer && items.length > 0) {
@@ -35,13 +36,13 @@ async function fetchPlatformFeed(platform, serviceUrl) {
 
     return items;
   } catch (err) {
-    console.error(`[FeedAggregator] ${platform} feed hatası:`, err.message);
+    log.error({ action: 'feed_fetch', platform, outcome: 'failure', err: err.message });
     return [];
   }
 }
 
 async function fetchAllFeeds() {
-  console.log('[FeedAggregator] Tüm platformlardan feed çekiliyor...');
+  log.info({ action: 'feed_fetch_all' }, 'Fetching feeds from all platforms');
 
   const results = await Promise.allSettled(
     Object.entries(PLATFORM_SERVICES).map(([platform, url]) =>
@@ -54,7 +55,7 @@ async function fetchAllFeeds() {
     summary[platform] = results[i].status === 'fulfilled' ? results[i].value.length : 0;
   });
 
-  console.log('[FeedAggregator] Tamamlandı:', summary);
+  log.info({ action: 'feed_fetch_all', outcome: 'success', summary });
   return summary;
 }
 
@@ -115,14 +116,14 @@ async function start() {
 
   // Periyodik feed yenileme
   cron.schedule(FEED_REFRESH_INTERVAL, () => {
-    fetchAllFeeds().catch(console.error);
+    fetchAllFeeds().catch((err) => log.error({ action: 'feed_fetch_all', outcome: 'failure', err: err.message }));
   });
 
   await app.listen({ port: process.env.PORT || 3010, host: '0.0.0.0' });
-  console.log(`[FeedAggregator] Started. Cron: ${FEED_REFRESH_INTERVAL}`);
+  log.info({ action: 'service_start', outcome: 'success', cronInterval: FEED_REFRESH_INTERVAL }, 'Feed aggregator started');
 
   // İlk çalıştırma
   setTimeout(() => fetchAllFeeds(), 5000);
 }
 
-start().catch(console.error);
+start().catch((err) => { log.error({ action: 'service_start', outcome: 'failure', err: err.message }); process.exit(1); });

+ 9 - 7
services/gateway/server.js

@@ -1,5 +1,7 @@
 require('dotenv').config();
-const app = require('fastify')({ logger: false });
+const { createLogger } = require('./utils/logger');
+const log = createLogger('gateway');
+const app = require('fastify')({ logger: log });
 const multipart = require('@fastify/multipart');
 const axios = require('axios');
 const fs = require('fs');
@@ -74,7 +76,7 @@ app.post('/upload', async (request, reply) => {
   try {
     await pipeline(data.file, fs.createWriteStream(filepath));
   } catch (err) {
-    console.error('[Gateway] Upload write error:', err.message);
+    app.log.error({ action: 'media_upload', outcome: 'failure', err: err.message });
     return reply.code(500).send({ error: 'Failed to save file' });
   }
 
@@ -92,7 +94,7 @@ app.post('/upload', async (request, reply) => {
     const db = await getDb();
     await db.collection('media_files').insertOne(record);
   } catch (err) {
-    console.error('[Gateway] Media metadata save error:', err.message);
+    app.log.error({ action: 'media_metadata_save', outcome: 'failure', err: err.message });
   }
 
   return { url: record.url, filename, originalName: data.filename, mimetype: data.mimetype, size: stat.size };
@@ -119,7 +121,7 @@ app.delete('/media/:filename', async (request, reply) => {
     fs.unlinkSync(filepath);
   } catch (err) {
     if (err.code !== 'ENOENT') {
-      console.error('[Gateway] Delete error:', err.message);
+      app.log.error({ action: 'media_delete', outcome: 'failure', err: err.message });
       return reply.code(500).send({ error: 'Failed to delete file' });
     }
     // Already gone from disk — still clean up DB record
@@ -217,7 +219,7 @@ app.get('/meta/token-expiry', async (request, reply) => {
         : null;
       accounts.push({ id: account.id, username: account.username, expiresAt, daysLeft, isValid: !!data.is_valid });
     } catch (err) {
-      console.error(`[Gateway] Token expiry check failed for ${account.username}:`, err.message);
+      app.log.warn({ action: 'token_expiry_check', platform: 'instagram', username: account.username, outcome: 'failure', err: err.message });
     }
   }
 
@@ -435,7 +437,7 @@ app.post('/', async (request, reply) => {
     await rabbitMQProducer.sendMessage('formatter', request.body.message);
     reply.send({ status: 'ok' });
   } catch (error) {
-    console.error('Error handling POST request:', error);
+    app.log.error({ action: 'legacy_post', outcome: 'failure', err: error.message });
     reply.status(500).send({ error: 'Internal Server Error' });
   }
 });
@@ -574,7 +576,7 @@ app.get('/auth/meta/callback', async (request, reply) => {
 
     reply.redirect(`${APP_BASE_URL}/settings?meta_discovery=1`);
   } catch (err) {
-    console.error('[Gateway] Meta OAuth error:', err.response?.data || err.message);
+    app.log.error({ action: 'meta_oauth_callback', platform: 'meta', outcome: 'failure', err: err.response?.data?.error?.message || err.message });
     reply.redirect(`${APP_BASE_URL}/settings?meta_error=${encodeURIComponent(err.message)}`);
   }
 });

+ 2 - 2
services/gateway/start.js

@@ -5,10 +5,10 @@ const { connect } = require('./utils/MongoDBConnector');
 async function start() {
   await connect();
   await app.listen({ port: 8084, host: '0.0.0.0' });
-  console.log('[Gateway] API service running on port 8084');
+  app.log.info({ action: 'service_start', port: 8084, outcome: 'success' }, 'Gateway API running');
 }
 
 start().catch((err) => {
-  console.error('[Gateway] Failed to start:', err);
+  app.log.error({ action: 'service_start', outcome: 'failure', err: err.message }, 'Gateway failed to start');
   process.exit(1);
 });

+ 1 - 1
services/instagram/index.js

@@ -109,7 +109,7 @@ class InstagramService extends BasePlatformService {
         );
       }
     } catch (err) {
-      console.error('[Instagram] MongoDB write error:', err.message);
+      this.app.log.error({ action: 'feed_write', platform: 'instagram', outcome: 'failure', err: err.message });
     }
 
     return allItems;

+ 4 - 1
services/linkedin/index.js

@@ -1,8 +1,11 @@
 const RabbitMQListener = require('./utils/RabbitMQListener');
+const { createLogger } = require('./utils/logger');
+
+const log = createLogger('linkedin');
 const rabbitMQListener = new RabbitMQListener();
 
 (async () => {
   await rabbitMQListener.listenToQueue('linkedin', (message) => {
-    console.log('Received message:', message);
+    log.info({ action: 'message_received', outcome: 'success' }, message);
   });
 })();

+ 1 - 1
services/mastodon/index.js

@@ -86,7 +86,7 @@ class MastodonService extends BasePlatformService {
         );
       }
     } catch (err) {
-      console.error('[Mastodon] MongoDB write error:', err.message);
+      this.app.log.error({ action: 'feed_write', platform: 'mastodon', outcome: 'failure', err: err.message });
     }
 
     return items;

+ 7 - 5
services/scheduler/index.js

@@ -4,6 +4,7 @@ const { Queue, Worker, QueueEvents } = require('bullmq');
 const IORedis = require('ioredis');
 const axios = require('axios');
 const { getDb, connect } = require('./utils/MongoDBConnector');
+const { createLogger } = require('./utils/logger');
 
 const REDIS_URL = process.env.REDIS_URL || 'redis://redis:6379';
 
@@ -16,7 +17,8 @@ const PLATFORM_SERVICES = {
   facebook:  process.env.FACEBOOK_SERVICE_URL  || 'http://facebook:3006',
 };
 
-const app = Fastify({ logger: false });
+const log = createLogger('scheduler');
+const app = Fastify({ logger: log });
 let postQueue;
 let redis;
 
@@ -28,7 +30,7 @@ async function processPostJob(job) {
   const { postId, content, destinations, platforms, media = [] } = job.data;
 
   const destList = destinations || (platforms || []).map((p) => ({ platform: p }));
-  console.log(`[Scheduler] Job ${job.id}: ${destList.map((d) => d.accountId ? `${d.platform}:${d.accountId}` : d.platform).join(', ')}`);
+  log.info({ action: 'job_process', jobId: job.id, destinations: destList.map((d) => d.accountId ? `${d.platform}:${d.accountId}` : d.platform) });
 
   const db = await getDb();
   const results = {};
@@ -157,11 +159,11 @@ async function start() {
 
   const worker = new Worker('post-queue', processPostJob, { connection: redis });
   worker.on('failed', (job, err) => {
-    console.error(`[Scheduler] Job ${job?.id} başarısız:`, err.message);
+    log.error({ action: 'job_process', jobId: job?.id, outcome: 'failure', err: err.message });
   });
 
   await app.listen({ port: process.env.PORT || 3011, host: '0.0.0.0' });
-  console.log('[Scheduler] Started on port 3011');
+  log.info({ action: 'service_start', port: 3011, outcome: 'success' }, 'Scheduler started');
 }
 
-start().catch(console.error);
+start().catch((err) => { log.error({ action: 'service_start', outcome: 'failure', err: err.message }); process.exit(1); });

+ 6 - 3
services/socket/index.js

@@ -1,21 +1,24 @@
 const socketIO = require('socket.io');
 const RabbitMQListener = require('./utils/RabbitMQListener');
 const EventEmitter = require('events');
+const { createLogger } = require('./utils/logger');
+
+const log = createLogger('socket');
 const socketEmitter = new EventEmitter();
 
 const rabbitMQListener = new RabbitMQListener();
 rabbitMQListener.listenToQueue('formattedMessages', (messages) => {
-  console.log('Received formatted Messages:', messages);
+  log.info({ action: 'message_received', queue: 'formattedMessages', outcome: 'success' });
   socketEmitter.emit('formattedMessages', messages);
 });
 
 const io = socketIO(8084);
 
 io.on('connection', (socket) => {
-  console.log('User Connected ' + socket.id);
+  log.info({ action: 'client_connect', socketId: socket.id });
 
   socket.on('disconnect', () => {
-    console.log('User Disconnected: ' + socket.id);
+    log.info({ action: 'client_disconnect', socketId: socket.id });
   });
 
   socketEmitter.on('formattedMessages', (messages) => {

+ 1 - 1
services/twitter/index.js

@@ -117,7 +117,7 @@ class TwitterService extends BasePlatformService {
         );
       }
     } catch (err) {
-      console.error('[Twitter] MongoDB write error:', err.message);
+      this.app.log.error({ action: 'feed_write', platform: 'twitter', outcome: 'failure', err: err.message });
     }
 
     return items;

+ 4 - 2
services/utils/BasePlatformService.js

@@ -1,5 +1,6 @@
 const Fastify = require('fastify');
 const RabbitMQConnector = require('./RabbitMQConnector');
+const { createLogger } = require('./logger');
 
 /**
  * BasePlatformService — tüm platform servisleri bu sınıftan extend eder.
@@ -14,7 +15,8 @@ class BasePlatformService extends RabbitMQConnector {
   constructor(platformName) {
     super();
     this.platformName = platformName;
-    this.app = Fastify({ logger: false });
+    this.log = createLogger(platformName);
+    this.app = Fastify({ logger: this.log });
     this._setupRoutes();
   }
 
@@ -56,7 +58,7 @@ class BasePlatformService extends RabbitMQConnector {
   async start(port = 3000) {
     await this.connect();
     await this.app.listen({ port, host: '0.0.0.0' });
-    console.log(`[${this.platformName}] Service started on port ${port}`);
+    this.app.log.info({ action: 'service_start', port, outcome: 'success' }, `${this.platformName} service started`);
   }
 
   // ─── Alt sınıfların override edeceği metodlar ───────────────────────────────

+ 5 - 2
services/utils/MongoDBConnector.js

@@ -1,4 +1,7 @@
 const { MongoClient } = require('mongodb');
+const { createLogger } = require('./logger');
+
+const log = createLogger('mongodb');
 
 const MONGODB_URL = process.env.MONGODB_URL || 'mongodb://mongodb:27017';
 const DB_NAME = process.env.MONGODB_DB || 'socialmedia';
@@ -12,7 +15,7 @@ async function connect() {
   client = new MongoClient(MONGODB_URL);
   await client.connect();
   db = client.db(DB_NAME);
-  console.log(`[MongoDB] Connected to ${DB_NAME}`);
+  log.info({ action: 'connect', outcome: 'success', database: DB_NAME });
   return db;
 }
 
@@ -26,7 +29,7 @@ async function disconnect() {
     await client.close();
     client = null;
     db = null;
-    console.log('[MongoDB] Disconnected');
+    log.info({ action: 'disconnect', outcome: 'success' });
   }
 }
 

+ 8 - 5
services/utils/RabbitMQConnector.js

@@ -1,4 +1,7 @@
 const amqp = require('amqplib');
+const { createLogger } = require('./logger');
+
+const log = createLogger('rabbitmq');
 
 class RabbitMQConnector {
   constructor() {
@@ -10,9 +13,9 @@ class RabbitMQConnector {
     try {
       this.connection = await amqp.connect('amqp://username:password@messageBroker');
       this.channel = await this.connection.createChannel();
-      console.log('Connected to RabbitMQ');
+      log.info({ action: 'connect', outcome: 'success' });
     } catch (error) {
-      console.error('Error connecting to RabbitMQ:', error);
+      log.error({ action: 'connect', outcome: 'failure', err: error.message });
     }
   }
 
@@ -20,15 +23,15 @@ class RabbitMQConnector {
     try {
       if (this.channel) {
         await this.channel.close();
-        console.log('Channel closed');
+        log.info({ action: 'channel_close', outcome: 'success' });
       }
 
       if (this.connection) {
         await this.connection.close();
-        console.log('Connection closed');
+        log.info({ action: 'disconnect', outcome: 'success' });
       }
     } catch (error) {
-      console.error('Error disconnecting from RabbitMQ:', error);
+      log.error({ action: 'disconnect', outcome: 'failure', err: error.message });
       throw error;
     }
   }

+ 22 - 0
services/utils/logger.js

@@ -0,0 +1,22 @@
+const pino = require('pino');
+
+/**
+ * Create a structured pino logger bound to a service name.
+ * Pass the returned instance to Fastify as { logger: log } so that
+ * app.log and the request-level child loggers all share the same config.
+ *
+ * Standard fields emitted on every line: service, level, time, msg
+ * Callers should add: action, platform, outcome, err (and any extras).
+ */
+function createLogger(service) {
+  return pino({
+    level: process.env.LOG_LEVEL || 'info',
+    base: { service },
+    timestamp: pino.stdTimeFunctions.isoTime,
+    formatters: {
+      level(label) { return { level: label }; },
+    },
+  });
+}
+
+module.exports = { createLogger };