Răsfoiți Sursa

dev: move some utils, LogService, to new module

KernelDeimos 5 luni în urmă
părinte
comite
7eb5e59d94

+ 1 - 1
src/backend/exports.js

@@ -46,8 +46,8 @@ module.exports = {
     Kernel,
     
     EssentialModules: [
-        CoreModule,
         Core2Module,
+        CoreModule,
         WebModule,
     ],
 

+ 0 - 2
src/backend/src/CoreModule.js

@@ -86,7 +86,6 @@ const install = async ({ services, app, useapi, modapi }) => {
     // call to services.registerService. We'll clean this up
     // in a future PR.
 
-    const { LogService } = require('./services/runtime-analysis/LogService');
     const { PagerService } = require('./services/runtime-analysis/PagerService');
     const { AlarmService } = require('./services/runtime-analysis/AlarmService');
     const { ErrorService } = require('./services/runtime-analysis/ErrorService');
@@ -140,7 +139,6 @@ const install = async ({ services, app, useapi, modapi }) => {
     // === Services which extend BaseService ===
     services.registerService('system-validation', SystemValidationService);
     services.registerService('server-health', ServerHealthService);
-    services.registerService('log-service', LogService);
     services.registerService('commands', CommandService);
     services.registerService('__api-filesystem', FilesystemAPIService);
     services.registerService('__api', PuterAPIService);

+ 13 - 2
src/backend/src/modules/core/Core2Module.js

@@ -1,13 +1,24 @@
 const { AdvancedBase } = require("@heyputer/putility");
 
+/**
+ * A replacement for CoreModule with as few external relative requires as possible.
+ * This will eventually be the successor to CoreModule, the main module for Puter's backend.
+ */
 class Core2Module extends AdvancedBase {
     async install (context) {
         // === LIBS === //
         const useapi = context.get('useapi');
-        useapi.def('std', require('./lib/__lib__.js'), { assign: true });
+
+        const lib = require('./lib/__lib__.js');
+        for ( const k in lib ) {
+            useapi.def(`core.${k}`, lib[k], { assign: true });
+        }
         
         // === SERVICES === //
-        // const services = context.get('services');
+        const services = context.get('services');
+
+        const { LogService } = require('./LogService.js');
+        services.registerService('log-service', LogService);
     }
 }
 

+ 28 - 55
src/backend/src/services/runtime-analysis/LogService.js → src/backend/src/modules/core/LogService.js

@@ -28,7 +28,8 @@ const LOG_LEVEL_SYSTEM = logSeverity(4, 'SYSTEM', '33;1', 'system');
 
 const winston = require('winston');
 const { Context } = require('../../util/context');
-const BaseService = require('../BaseService');
+const BaseService = require('../../services/BaseService');
+const { stringify_log_entry } = require('./lib/log');
 require('winston-daily-rotate-file');
 
 const WINSTON_LEVELS = {
@@ -139,58 +140,9 @@ class LogContext {
     }
 }
 
-let log_epoch = Date.now();
 /**
 * Timestamp in milliseconds since the epoch, used for calculating log entry duration.
 */
-const stringify_log_entry = ({ prefix, log_lvl, crumbs, message, fields, objects }) => {
-    const { colorize } = require('json-colorizer');
-
-    let lines = [], m;
-    /**
-    * Stringifies a log entry into a formatted string for console output.
-    * @param {Object} logEntry - The log entry object containing:
-    *   @param {string} [prefix] - Optional prefix for the log message.
-    *   @param {Object} log_lvl - Log level object with properties for label, escape code, etc.
-    *   @param {string[]} crumbs - Array of context crumbs.
-    *   @param {string} message - The log message.
-    *   @param {Object} fields - Additional fields to be included in the log.
-    *   @param {Object} objects - Objects to be logged.
-    * @returns {string} A formatted string representation of the log entry.
-    */
-    const lf = () => {
-        if ( ! m ) return;
-        lines.push(m);
-        m = '';
-    }
-
-    m = prefix ? `${prefix} ` : '';
-    m += `\x1B[${log_lvl.esc}m[${log_lvl.label}\x1B[0m`;
-    for ( const crumb of crumbs ) {
-        m += `::${crumb}`;
-    }
-    m += `\x1B[${log_lvl.esc}m]\x1B[0m`;
-    if ( fields.timestamp ) {
-        // display seconds since logger epoch
-        const n = (fields.timestamp - log_epoch) / 1000;
-        m += ` (${n.toFixed(3)}s)`;
-    }
-    m += ` ${message} `;
-    lf();
-    for ( const k in fields ) {
-        if ( k === 'timestamp' ) continue;
-        let v; try {
-            v = colorize(JSON.stringify(fields[k]));
-        } catch (e) {
-            v = '' + fields[k];
-        }
-        m += ` \x1B[1m${k}:\x1B[0m ${v}`;
-        lf();
-    }
-    return lines.join('\n');
-};
-
-
 
 /**
 * @class DevLogger
@@ -385,9 +337,18 @@ class LogService extends BaseService {
         this.loggers = [];
         this.bufferLogger = null;
     }
+    
+    /**
+     * Registers a custom logging middleware with the LogService.
+     * @param {*} callback - The callback function that modifies log parameters before delegation.
+     */
     register_log_middleware (callback) {
         this.loggers[0] = new CustomLogger(this.loggers[0], callback);
     }
+    
+    /**
+     * Registers logging commands with the command service.
+     */
     ['__on_boot.consolidation'] () {
         const commands = this.services.get('commands');
         commands.registerCommands('logs', [
@@ -530,6 +491,13 @@ class LogService extends BaseService {
         globalThis.root_context.set('logger', this.create('root-context'));
     }
 
+    /**
+     * Create a new log context with the specified prefix
+     * 
+     * @param {1} prefix - The prefix for the log context
+     * @param {*} fields - Optional fields to include in the log context
+     * @returns {LogContext} A new log context with the specified prefix and fields
+     */
     create (prefix, fields = {}) {
         const logContext = new LogContext(
             this,
@@ -622,6 +590,12 @@ class LogService extends BaseService {
         throw new Error('Unable to create or find log directory');
     }
 
+    /**
+    * Generates a sanitized file path for log files.
+    * 
+    * @param {string} name - The name of the log file, which will be sanitized to remove any path characters.
+    * @returns {string} A sanitized file path within the log directory.
+    */
     get_log_file (name) {
         // sanitize name: cannot contain path characters
         name = name.replace(/[^a-zA-Z0-9-_]/g, '_');
@@ -630,11 +604,10 @@ class LogService extends BaseService {
 
 
     /**
-    * Generates a sanitized file path for log files.
-    * 
-    * @param {string} name - The name of the log file, which will be sanitized to remove any path characters.
-    * @returns {string} A sanitized file path within the log directory.
-    */
+     * Get the most recent log entries from the buffer maintained by the LogService.
+     * By default, the buffer contains the last 20 log entries.
+     * @returns 
+     */
     get_log_buffer () {
         return this.bufferLogger.buffer;
     }

+ 4 - 1
src/backend/src/modules/core/lib/__lib__.js

@@ -1,3 +1,6 @@
 module.exports = {
-    string: require('./string.js'),
+    util: {
+        strutil: require('./string.js'),
+        logutil: require('./log.js'),
+    },
 };

+ 73 - 0
src/backend/src/modules/core/lib/log.js

@@ -0,0 +1,73 @@
+// METADATA // {"def":"core.util.logutil","ai-commented":{"service":"openai-completion","model":"gpt-4o"}}
+/*
+ * Copyright (C) 2024 Puter Technologies Inc.
+ *
+ * This file is part of Puter.
+ *
+ * Puter is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+const log_epoch = Date.now();
+
+/**
+* Stringifies a log entry into a formatted string for console output.
+* @param {Object} logEntry - The log entry object containing:
+*   @param {string} [prefix] - Optional prefix for the log message.
+*   @param {Object} log_lvl - Log level object with properties for label, escape code, etc.
+*   @param {string[]} crumbs - Array of context crumbs.
+*   @param {string} message - The log message.
+*   @param {Object} fields - Additional fields to be included in the log.
+*   @param {Object} objects - Objects to be logged.
+* @returns {string} A formatted string representation of the log entry.
+*/
+const stringify_log_entry = ({ prefix, log_lvl, crumbs, message, fields, objects }) => {
+    const { colorize } = require('json-colorizer');
+
+    let lines = [], m;
+
+    const lf = () => {
+        if ( ! m ) return;
+        lines.push(m);
+        m = '';
+    }
+
+    m = prefix ? `${prefix} ` : '';
+    m += `\x1B[${log_lvl.esc}m[${log_lvl.label}\x1B[0m`;
+    for ( const crumb of crumbs ) {
+        m += `::${crumb}`;
+    }
+    m += `\x1B[${log_lvl.esc}m]\x1B[0m`;
+    if ( fields.timestamp ) {
+        // display seconds since logger epoch
+        const n = (fields.timestamp - log_epoch) / 1000;
+        m += ` (${n.toFixed(3)}s)`;
+    }
+    m += ` ${message} `;
+    lf();
+    for ( const k in fields ) {
+        if ( k === 'timestamp' ) continue;
+        let v; try {
+            v = colorize(JSON.stringify(fields[k]));
+        } catch (e) {
+            v = '' + fields[k];
+        }
+        m += ` \x1B[1m${k}:\x1B[0m ${v}`;
+        lf();
+    }
+    return lines.join('\n');
+};
+
+module.exports = {
+    stringify_log_entry,
+    log_epoch,
+};

+ 21 - 3
src/backend/src/modules/core/lib/string.js

@@ -1,3 +1,4 @@
+// METADATA // {"def":"core.util.strutil","ai-params":{"service":"claude"},"ai-commented":{"service":"claude"}}
 /*
  * Copyright (C) 2024 Puter Technologies Inc.
  *
@@ -16,9 +17,13 @@
  * You should have received a copy of the GNU Affero General Public License
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
-// Convenience function for quoting strings in error messages.
-// Turns a string like this: some`value`
-// Into a string like this: `some\`value\``
+
+/**
+* Quotes a string value, handling special cases for undefined, null, functions, objects and numbers.
+* Escapes quotes and returns a JSON-stringified version with quote character normalization.
+* @param {*} str - The value to quote
+* @returns {string} The quoted string representation
+*/
 const quot = (str) => {
     if ( str === undefined ) return '[undefined]';
     if ( str === null ) return '[null]';
@@ -34,11 +39,24 @@ const quot = (str) => {
     return str;
 }
 
+
+/**
+* Creates an OSC 8 hyperlink sequence for terminal output
+* @param {string} url - The URL to link to
+* @param {string} [text] - Optional display text, defaults to URL if not provided
+* @returns {string} Terminal escape sequence containing the hyperlink
+*/
 const osclink = (url, text) => {
     if ( ! text ) text = url;
     return `\x1B]8;;${url}\x1B\\${text}\x1B]8;;\x1B\\`;
 }
 
+
+/**
+* Formats a number as a USD currency string with appropriate decimal places
+* @param {number} amount - The amount to format
+* @returns {string} The formatted USD string
+*/
 const format_as_usd = (amount) => {
     if ( amount < 0.01 ) {
         if ( amount < 0.00001 ) {

+ 16 - 0
src/backend/src/modules/web/SocketioService.js

@@ -1,3 +1,4 @@
+// METADATA // {"ai-params":{"service":"claude"},"ai-commented":{"service":"claude"}}
 const BaseService = require('../../services/BaseService');
 
 /**
@@ -29,6 +30,15 @@ class SocketioService extends BaseService {
         });
     }
     
+
+    /**
+    * Sends a message to specified socket(s) or room(s)
+    * 
+    * @param {Array|Object} socket_specifiers - Single or array of objects specifying target sockets/rooms
+    * @param {string} key - The event key/name to emit
+    * @param {*} data - The data payload to send
+    * @returns {Promise<void>}
+    */
     async send (socket_specifiers, key, data) {
         const svc_getUser = this.services.get('get-user');
         
@@ -47,6 +57,12 @@ class SocketioService extends BaseService {
         }
     }
     
+    /**
+     * Checks if the specified socket or room exists
+     * 
+     * @param {Object} socket_specifier - The socket specifier object
+     * @returns {boolean} True if the socket exists, false otherwise
+     */
     has (socket_specifier) {
         if ( socket_specifier.room ) {
             const room = this.io.sockets.adapter.rooms.get(socket_specifier.room);

+ 1 - 1
src/backend/src/modules/web/WebServerService.js

@@ -38,7 +38,7 @@ const relative_require = require;
 */
 class WebServerService extends BaseService {
     static USE = {
-        strutil: 'std.string',
+        strutil: 'core.util.strutil',
     }
 
     static MODULES = {

+ 4 - 2
src/backend/src/services/MakeProdDebuggingLessAwfulService.js

@@ -19,7 +19,6 @@
  */
 const { Context } = require("../util/context");
 const BaseService = require("./BaseService");
-const { stringify_log_entry } = require("./runtime-analysis/LogService");
 
 /**
  * This service registers a middleware that will apply the value of
@@ -38,6 +37,9 @@ const { stringify_log_entry } = require("./runtime-analysis/LogService");
 * also handles the creation and management of debug-specific log files for better traceability.
 */
 class MakeProdDebuggingLessAwfulService extends BaseService {
+    static USE = {
+        logutil: 'core.util.logutil',
+    }
     static MODULES = {
         fs: require('fs'),
     }
@@ -102,7 +104,7 @@ class MakeProdDebuggingLessAwfulService extends BaseService {
             try {
                 await this.modules.fs.promises.appendFile(
                     outfile,
-                    stringify_log_entry(log_details) + '\n',
+                    this.logutil.stringify_log_entry(log_details) + '\n',
                 );
             } catch ( e ) {
                 console.error(e);

+ 4 - 2
src/backend/src/services/runtime-analysis/AlarmService.js

@@ -26,7 +26,6 @@ const fs = require('fs');
 
 const { fallbackRead } = require('../../util/files.js');
 const { generate_identifier } = require('../../util/identifier.js');
-const { stringify_log_entry } = require('./LogService.js');
 const BaseService = require('../BaseService.js');
 const { split_lines } = require('../../util/stdioutil.js');
 const { Context } = require('../../util/context.js');
@@ -36,6 +35,9 @@ const { Context } = require('../../util/context.js');
 * @classdesc AlarmService class is responsible for managing alarms. It provides methods for creating, clearing, and handling alarms.
 */
 class AlarmService extends BaseService {
+    static USE = {
+        logutil: 'core.util.logutil',
+    }
     /**
     * This method initializes the AlarmService by setting up its internal data structures and initializing any required dependencies.
     *
@@ -492,7 +494,7 @@ class AlarmService extends BaseService {
                     }
                     log.log(`┏━━ Logs before: ${alarm.id_string} ━━━━`);
                     for ( const lg of occurance.logs ) {
-                        log.log("┃ " + stringify_log_entry(lg));
+                        log.log("┃ " + this.logutil.stringify_log_entry(lg));
                     }
                     log.log(`┗━━ Logs before: ${alarm.id_string} ━━━━`);
                 },