new(all): Log per bot started. #299.

This commit is contained in:
rodrigorodriguez 2023-02-12 14:31:21 -03:00
parent b7cad9a67f
commit 3d500051fa
8 changed files with 197 additions and 79 deletions

View file

@ -238,6 +238,24 @@ export class AdminDialog extends IGBDialog {
])
);
min.dialogs.add(
new WaterfallDialog('/logs', [
async step => {
if (step.context.activity.channelId !== 'msteams' && process.env.ENABLE_AUTH) {
return await step.beginDialog('/auth');
} else {
return await step.next(step.options);
}
},
async step => {
const logs = await min.core['getLatestLogs']();
await min.conversationalService.sendText(min, step, logs);
return await step.replaceDialog('/ask', { isReturning: true });
}
]));
min.dialogs.add(
new WaterfallDialog('/publish', [
async step => {

View file

@ -32,7 +32,7 @@
'use strict';
import { GBLog, GBMinInstance, GBService, IGBCoreService, GBDialogStep } from 'botlib';
import { GBMinInstance, GBService, IGBCoreService, GBDialogStep } from 'botlib';
import * as Fs from 'fs';
import { GBServer } from '../../../src/app.js';
import { GBDeployer } from '../../core.gbapp/services/GBDeployer.js';
@ -50,9 +50,7 @@ import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js';
import pkg from 'swagger-client';
import { DialogKeywords } from './DialogKeywords.js';
import { KeywordsExpressions } from './KeywordsExpressions.js';
const { Swagger } = pkg;
import { GBLogEx } from '../../core.gbapp/services/GBLogEx.js';
/**
* @fileoverview Decision was to priorize security(isolation) and debugging,
@ -129,7 +127,7 @@ export class GBVMService extends GBService {
}`;
Fs.writeFileSync(urlJoin(folder, 'package.json'), packageJson);
GBLog.info(`BASIC: Installing .gbdialog node_modules for ${min.botId}...`);
GBLogEx.info(min, `BASIC: Installing .gbdialog node_modules for ${min.botId}...`);
const npmPath = urlJoin(process.env.PWD, 'node_modules', '.bin', 'npm');
child_process.execSync(`${npmPath} install`, { cwd: folder });
}
@ -139,7 +137,7 @@ export class GBVMService extends GBService {
const fullFilename = urlJoin(folder, filename);
if (process.env.GBDIALOG_HOTSWAP) {
Fs.watchFile(fullFilename, async () => {
await this.translateBASIC(fullFilename, mainName, min.botId);
await this.translateBASIC(fullFilename, mainName, min);
const parsedCode: string = Fs.readFileSync(jsfile, 'utf8');
min.sandBoxMap[mainName.toLowerCase().trim()] = parsedCode;
});
@ -152,10 +150,10 @@ export class GBVMService extends GBService {
const jsStat = Fs.statSync(jsfile);
const interval = 30000; // If compiled is older 30 seconds, then recompile.
if (compiledAt.isFile() && compiledAt['mtimeMs'] > jsStat['mtimeMs'] + interval) {
await this.translateBASIC(fullFilename, mainName, min.botId);
await this.translateBASIC(fullFilename, mainName, min);
}
} else {
await this.translateBASIC(fullFilename, mainName, min.botId);
await this.translateBASIC(fullFilename, mainName, min);
}
const parsedCode: string = Fs.readFileSync(jsfile, 'utf8');
min.sandBoxMap[mainName.toLowerCase().trim()] = parsedCode;
@ -163,7 +161,7 @@ export class GBVMService extends GBService {
});
}
public async translateBASIC(filename: any, mainName: string, botId: string) {
public async translateBASIC(filename: any, mainName: string, min:GBMinInstance) {
// Converts General Bots BASIC into regular VBS
let basicCode: string = Fs.readFileSync(filename, 'utf8');
@ -204,10 +202,10 @@ export class GBVMService extends GBService {
// Interprocess communication from local HTTP to the BotServer.
const dk = rest.createClient('http://localhost:1111/api/v2/${botId}/dialog');
const sys = rest.createClient('http://localhost:1111/api/v2/${botId}/system');
const wa = rest.createClient('http://localhost:1111/api/v2/${botId}/webautomation');
const img = rest.createClient('http://localhost:1111/api/v2/${botId}/imagprocessing');
const dk = rest.createClient('http://localhost:1111/api/v2/${min.botId}/dialog');
const sys = rest.createClient('http://localhost:1111/api/v2/${min.botId}/system');
const wa = rest.createClient('http://localhost:1111/api/v2/${min.botId}/webautomation');
const img = rest.createClient('http://localhost:1111/api/v2/${min.botId}/imagprocessing');
// Local variables.
@ -245,7 +243,7 @@ export class GBVMService extends GBService {
`;
Fs.writeFileSync(jsfile, code);
GBLog.info(`[GBVMService] Finished loading of ${filename}, JavaScript from Word: \n ${code}`);
GBLogEx.info(min, `[GBVMService] Finished loading of ${filename}, JavaScript from Word: \n ${code}`);
}
public static getMethodNameFromVBSFilename(filename: string) {
@ -288,7 +286,7 @@ export class GBVMService extends GBService {
public async convert(code: string) {
// Start and End of VB2TS tags of processing.
code = process.env.ENABLE_AUTH ? `hear gbLogin as login\n${code}` : code;
code = process.env.ENABLE_AUTH ? `hear GBLogExin as login\n${code}` : code;
var lines = code.split('\n');
const keywords = KeywordsExpressions.getKeywords();
let current = 41;

View file

@ -43,7 +43,7 @@ import { LanguageDialog } from './dialogs/LanguageDialog.js';
import { SwitchBotDialog } from './dialogs/SwitchBot.js';
import { WelcomeDialog } from './dialogs/WelcomeDialog.js';
import { WhoAmIDialog } from './dialogs/WhoAmIDialog.js';
import { GuaribasChannel, GuaribasException, GuaribasInstance, GuaribasPackage } from './models/GBModel.js';
import { GuaribasChannel, GuaribasInstance, GuaribasLog, GuaribasPackage } from './models/GBModel.js';
/**
* Package for core.gbapp.
@ -53,7 +53,7 @@ export class GBCorePackage implements IGBPackage {
public CurrentEngineName = 'guaribas-1.0.0';
public async loadPackage (core: IGBCoreService, sequelize: Sequelize): Promise<void> {
core.sequelize.addModels([GuaribasInstance, GuaribasPackage, GuaribasChannel, GuaribasException]);
core.sequelize.addModels([GuaribasInstance, GuaribasPackage, GuaribasChannel, GuaribasLog]);
}
public async getDialogs (min: GBMinInstance) {

View file

@ -326,15 +326,18 @@ export class GuaribasChannel extends Model<GuaribasChannel> {
*/
@Table
//tslint:disable-next-line:max-classes-per-file
export class GuaribasException extends Model<GuaribasException> {
export class GuaribasLog extends Model<GuaribasLog> {
@PrimaryKey
@AutoIncrement
@Column(DataType.INTEGER)
declare exceptionId: number;
declare logId: number;
@Column(DataType.STRING(255))
@Column(DataType.STRING(1024))
declare message: string;
@Column(DataType.STRING(1))
declare kind: string;
@ForeignKey(() => GuaribasInstance)
@Column(DataType.INTEGER)
declare instanceId: number;

View file

@ -50,7 +50,7 @@ import { GBCustomerSatisfactionPackage } from '../../customer-satisfaction.gbapp
import { GBKBPackage } from '../../kb.gbapp/index.js';
import { GBSecurityPackage } from '../../security.gbapp/index.js';
import { GBWhatsappPackage } from '../../whatsapp.gblib/index.js';
import { GuaribasInstance } from '../models/GBModel.js';
import { GuaribasInstance, GuaribasLog} from '../models/GBModel.js';
import { GBConfigService } from './GBConfigService.js';
import { GBAzureDeployerPackage } from '../../azuredeployer.gbapp/index.js';
import { GBSharePointPackage } from '../../sharepoint.gblib/index.js';
@ -102,16 +102,16 @@ export class GBCoreService implements IGBCoreService {
/**
*
*/
constructor () {
constructor() {
this.adminService = new GBAdminService(this);
}
public async ensureInstances (instances: IGBInstance[], bootInstance: any, core: IGBCoreService) {}
public async ensureInstances(instances: IGBInstance[], bootInstance: any, core: IGBCoreService) {}
/**
* Gets database config and connect to storage. Currently two databases
* are available: SQL Server and SQLite.
*/
public async initStorage (): Promise<any> {
public async initStorage(): Promise<any> {
this.dialect = GBConfigService.get('STORAGE_DIALECT');
let host: string | undefined;
@ -177,7 +177,7 @@ export class GBCoreService implements IGBCoreService {
* Checks wheather storage is acessible or not and opens firewall
* in case of any connection block.
*/
public async checkStorage (installationDeployer: IGBInstallationDeployer) {
public async checkStorage(installationDeployer: IGBInstallationDeployer) {
try {
await this.sequelize.authenticate();
} catch (error) {
@ -195,7 +195,7 @@ export class GBCoreService implements IGBCoreService {
/**
* Syncronizes structure between model and tables in storage.
*/
public async syncDatabaseStructure () {
public async syncDatabaseStructure() {
if (GBConfigService.get('STORAGE_SYNC') === 'true') {
const alter = GBConfigService.get('STORAGE_SYNC_ALTER') === 'true';
GBLog.info('Syncing database...');
@ -213,7 +213,29 @@ export class GBCoreService implements IGBCoreService {
/**
* Loads all items to start several listeners.
*/
public async loadInstances (): Promise<IGBInstance[]> {
public async getLatestLogs(instanceId: number): Promise<string> {
const options = {
where: {
instanceId: instanceId,
state: 'active',
created: {
[Op.gt]: new Date(Date.now() - 60 * 60 * 1000 * 48) // Latest 48 hours.
}
}
};
const list = await GuaribasLog.findAll(options);
let out = 'General Bots Log\n';
await CollectionUtil.asyncForEach(list, async e => {
out = `${out}\n${e.createdAt} - ${e.message}`;
});
return out;
}
/**
* Loads all items to start several listeners.
*/
public async loadInstances(): Promise<IGBInstance[]> {
if (process.env.LOAD_ONLY !== undefined) {
const bots = process.env.LOAD_ONLY.split(`;`);
const and = [];
@ -236,7 +258,7 @@ export class GBCoreService implements IGBCoreService {
/**
* Loads just one Bot instance by its internal Id.
*/
public async loadInstanceById (instanceId: number): Promise<IGBInstance> {
public async loadInstanceById(instanceId: number): Promise<IGBInstance> {
const options = { where: { instanceId: instanceId, state: 'active' } };
return await GuaribasInstance.findOne(options);
@ -244,7 +266,7 @@ export class GBCoreService implements IGBCoreService {
/**
* Loads just one Bot instance.
*/
public async loadInstanceByActivationCode (code: string): Promise<IGBInstance> {
public async loadInstanceByActivationCode(code: string): Promise<IGBInstance> {
let options = { where: { activationCode: code, state: 'active' } };
return await GuaribasInstance.findOne(options);
@ -252,7 +274,7 @@ export class GBCoreService implements IGBCoreService {
/**
* Loads just one Bot instance.
*/
public async loadInstanceByBotId (botId: string): Promise<IGBInstance> {
public async loadInstanceByBotId(botId: string): Promise<IGBInstance> {
const options = { where: {} };
options.where = { botId: botId, state: 'active' };
@ -264,7 +286,7 @@ export class GBCoreService implements IGBCoreService {
* first startup, when user is asked some questions to create the
* full base environment.
*/
public async writeEnv (instance: IGBInstance) {
public async writeEnv(instance: IGBInstance) {
const env = `
ADDITIONAL_DEPLOY_PATH=
ADMIN_PASS=${instance.adminPass}
@ -294,7 +316,7 @@ ENDPOINT_UPDATE=true
* when calling back from web services. This ensures that reverse proxy is
* established.
*/
public async ensureProxy (port): Promise<string> {
public async ensureProxy(port): Promise<string> {
try {
if (Fs.existsSync('node_modules/ngrok/bin/ngrok.exe') || Fs.existsSync('node_modules/ngrok/bin/ngrok')) {
return await ngrok.connect({ port: port });
@ -315,7 +337,7 @@ ENDPOINT_UPDATE=true
* Setup generic web hooks so .gbapps can expose application logic
* and get called on demand.
*/
public installWebHook (isGet: boolean, url: string, callback: any) {
public installWebHook(isGet: boolean, url: string, callback: any) {
if (isGet) {
GBServer.globals.server.get(url, (req, res) => {
callback(req, res);
@ -331,7 +353,7 @@ ENDPOINT_UPDATE=true
* Defines the entry point dialog to be called whenever a user
* starts talking to the bot.
*/
public setEntryPointDialog (dialogName: string) {
public setEntryPointDialog(dialogName: string) {
GBServer.globals.entryPointDialog = dialogName;
}
@ -339,14 +361,14 @@ ENDPOINT_UPDATE=true
* Replaces the default web application root path used to start the GB
* with a custom home page.
*/
public setWWWRoot (localPath: string) {
public setWWWRoot(localPath: string) {
GBServer.globals.wwwroot = localPath;
}
/**
* Removes a bot instance from storage.
*/
public async deleteInstance (botId: string) {
public async deleteInstance(botId: string) {
const options = { where: {} };
options.where = { botId: botId };
await GuaribasInstance.destroy(options);
@ -356,7 +378,7 @@ ENDPOINT_UPDATE=true
* Saves a bot instance object to the storage handling
* multi-column JSON based store 'params' field.
*/
public async saveInstance (fullInstance: any) {
public async saveInstance(fullInstance: any) {
const options = { where: {} };
options.where = { botId: fullInstance.botId };
let instance = await GuaribasInstance.findOne(options);
@ -377,7 +399,7 @@ ENDPOINT_UPDATE=true
/**
* Loads all bot instances from object storage, if it's formatted.
*/
public async loadAllInstances (
public async loadAllInstances(
core: IGBCoreService,
installationDeployer: IGBInstallationDeployer,
proxyAddress: string
@ -431,7 +453,7 @@ ENDPOINT_UPDATE=true
/**
* Loads all system packages from 'packages' folder.
*/
public async loadSysPackages (core: GBCoreService): Promise<IGBPackage[]> {
public async loadSysPackages(core: GBCoreService): Promise<IGBPackage[]> {
// NOTE: if there is any code before this line a semicolon
// will be necessary before this line.
// Loads all system packages.
@ -469,7 +491,7 @@ ENDPOINT_UPDATE=true
* Verifies that an complex global password has been specified
* before starting the server.
*/
public ensureAdminIsSecured () {
public ensureAdminIsSecured() {
const password = GBConfigService.get('ADMIN_PASS');
if (!GBAdminService.StrongRegex.test(password)) {
throw new Error(
@ -484,7 +506,7 @@ ENDPOINT_UPDATE=true
* So a base main bot is always deployed and will act as root bot for
* configuration tree with three levels: .env > root bot > all other bots.
*/
public async createBootInstance (
public async createBootInstance(
core: GBCoreService,
installationDeployer: IGBInstallationDeployer,
proxyAddress: string
@ -519,7 +541,7 @@ ENDPOINT_UPDATE=true
/**
* Helper to get the web browser onpened in UI interfaces.
*/
public openBrowserInDevelopment () {
public openBrowserInDevelopment() {
if (process.env.NODE_ENV === 'development') {
open('http://localhost:4242');
}
@ -540,24 +562,19 @@ ENDPOINT_UPDATE=true
* // ' FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId1]) ON DELETE NO ACTION,' +
* // ' FOREIGN KEY ([instanceId]) REFERENCES [Instance] ([instanceId]) ON DELETE NO ACTION)'
*/
private createTableQueryOverride (tableName, attributes, options): string {
private createTableQueryOverride(tableName, attributes, options): string {
let sql: string = this.createTableQuery.apply(this.queryGenerator, [tableName, attributes, options]);
const re1 = /CREATE\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql);
if (matches !== null) {
const table = matches[1];
const re2 = /PRIMARY\s+KEY\s+\(\[[^\]]*\](?:,\s*\[[^\]]*\])*\)/;
sql = sql.replace(
re2,
(match: string, ...args: any[]): string => {
sql = sql.replace(re2, (match: string, ...args: any[]): string => {
return `CONSTRAINT [${table}_pk] ${match}`;
}
);
});
const re3 = /FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
const re4 = /\[([^\]]*)\]/g;
sql = sql.replace(
re3,
(match: string, ...args: any[]): string => {
sql = sql.replace(re3, (match: string, ...args: any[]): string => {
const fkcols = args[0];
let fkname = table;
let matches2 = re4.exec(fkcols);
@ -567,8 +584,7 @@ ENDPOINT_UPDATE=true
}
return `CONSTRAINT [${fkname}_fk] FOREIGN KEY (${fkcols})`;
}
);
});
}
return sql;
@ -582,7 +598,7 @@ ENDPOINT_UPDATE=true
* ' CONSTRAINT [invalid2] FOREIGN KEY ([groupId1], [groupId2]) REFERENCES [Group] ([groupId1], [groupId2]) ON DELETE NO ACTION, ' +
* ' CONSTRAINT [invalid3] FOREIGN KEY ([instanceId1]) REFERENCES [Instance] ([instanceId1]) ON DELETE NO ACTION'
*/
private changeColumnQueryOverride (tableName, attributes): string {
private changeColumnQueryOverride(tableName, attributes): string {
let sql: string = this.changeColumnQuery.apply(this.queryGenerator, [tableName, attributes]);
const re1 = /ALTER\s+TABLE\s+\[([^\]]*)\]/;
const matches = re1.exec(sql);
@ -590,9 +606,7 @@ ENDPOINT_UPDATE=true
const table = matches[1];
const re2 = /(ADD\s+)?CONSTRAINT\s+\[([^\]]*)\]\s+FOREIGN\s+KEY\s+\((\[[^\]]*\](?:,\s*\[[^\]]*\])*)\)/g;
const re3 = /\[([^\]]*)\]/g;
sql = sql.replace(
re2,
(match: string, ...args: any[]): string => {
sql = sql.replace(re2, (match: string, ...args: any[]): string => {
const fkcols = args[2];
let fkname = table;
let matches2 = re3.exec(fkcols);
@ -602,8 +616,7 @@ ENDPOINT_UPDATE=true
}
return `${args[0] ? args[0] : ''}CONSTRAINT [${fkname}_fk] FOREIGN KEY (${fkcols})`;
}
);
});
}
return sql;
@ -612,7 +625,7 @@ ENDPOINT_UPDATE=true
/**
* Opens storage firewall used by the server when starting to get root bot instance.
*/
private async openStorageFrontier (installationDeployer: IGBInstallationDeployer) {
private async openStorageFrontier(installationDeployer: IGBInstallationDeployer) {
const group = GBConfigService.get('CLOUD_GROUP');
const serverName = GBConfigService.get('STORAGE_SERVER').split('.database.windows.net')[0];
await installationDeployer.openStorageFirewall(group, serverName);
@ -625,7 +638,7 @@ ENDPOINT_UPDATE=true
* @param name Name of param to get from instance.
* @param defaultValue Value returned when no param is defined in Config.xlsx.
*/
public getParam<T> (instance: IGBInstance, name: string, defaultValue?: T): any {
public getParam<T>(instance: IGBInstance, name: string, defaultValue?: T): any {
let value = null;
if (instance.params) {
const params = JSON.parse(instance.params);

View file

@ -0,0 +1,85 @@
/*****************************************************************************\
| ( )_ _ |
| _ _ _ __ _ _ __ ___ ___ _ _ | ,_)(_) ___ _ _ _ |
| ( '_`\ ( '__)/'_` ) /'_ `\/' _ ` _ `\ /'_` )| | | |/',__)/ \ /`\ /'_`\ |
| | (_) )| | ( (_| |( (_) || ( ) ( ) |( (_| || |_ | |\__, \| |*| |( (_) ) |
| | ,__/'(_) `\__,_)`\__ |(_) (_) (_)`\__,_)`\__)(_)(____/(_) (_)`\___/' |
| | | ( )_) | |
| (_) \___/' |
| |
| General Bots Copyright (c) Pragmatismo.io. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| 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. |
| |
| "General Bots" is a registered trademark of Pragmatismo.io. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
/**
* @fileoverview General Bots server core.
*/
'use strict';
import { GBLog, IGBInstance } from "botlib";
import { GuaribasLog } from "../models/GBModel";
export class GBLogEx {
public static async error(minOrInstanceId: any, message: string) {
GBLog.error(message);
if (typeof minOrInstanceId === 'object') {
minOrInstanceId = minOrInstanceId.instance.instanceId;
}
await this.log(minOrInstanceId, 'e', message);
}
public static async debug(minOrInstanceId: any, message: string) {
GBLog.debug(message);
if (typeof minOrInstanceId === 'object') {
minOrInstanceId = minOrInstanceId.instance.instanceId;
}
await this.log(minOrInstanceId, 'd', message);
}
public static async info(minOrInstanceId: any, message: string) {
GBLog.info(message);
if (typeof minOrInstanceId === 'object') {
minOrInstanceId = minOrInstanceId.instance.instanceId;
}
await this.log(minOrInstanceId, 'i', message);
}
public static async verbose(minOrInstanceId: any, message: string) {
GBLog.verbose(message);
if (typeof minOrInstanceId === 'object') {
minOrInstanceId = minOrInstanceId.instance.instanceId;
}
await this.log(minOrInstanceId, 'v', message);
}
/**
* Finds and update user agent information to a next available person.
*/
public static async log(instance: IGBInstance, kind: string, message: string): Promise<GuaribasLog> {
return await GuaribasLog.create(<GuaribasLog>{
instanceId: instance.instanceId,
message: message,
kind: kind
});
}
}

View file

@ -101,7 +101,7 @@ export class AskDialog extends IGBDialog {
if (step.options && step.options.firstTime) {
text = Messages[locale].ask_first_time;
} else if (step.options && step.options.isReturning) {
text = ''; // REMOVED: Messages[locale].anything_else;
text = Messages[locale].anything_else;
} else if (step.options && step.options.emptyPrompt) {
text = '';
} else if (user.subjects.length > 0) {

View file

@ -29,6 +29,7 @@
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
/**
* @fileoverview General Bots server core.
*/