dev: move some utils, LogService, to new module

This commit is contained in:
KernelDeimos 2024-12-04 15:53:35 -05:00
parent 5c3a65060d
commit 7eb5e59d94
11 changed files with 165 additions and 69 deletions

View File

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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}

View File

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

View File

@ -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,
};

View File

@ -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 ) {

View File

@ -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);

View File

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

View File

@ -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);

View File

@ -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} ━━━━`);
},