refs #5563 config check fixes, options added
gitea/mylogger/pipeline/head This commit looks good Details

New options: logId, logRelation, showRelation and logFields
This commit is contained in:
Juan Ferrer 2023-06-08 18:29:08 +02:00
parent 512bd56a2c
commit ab775da88a
6 changed files with 94 additions and 51 deletions

View File

@ -1,3 +1,6 @@
logId: true
logRelation: true
logMainShowField: false
upperCaseTable: true upperCaseTable: true
userField: editorFk userField: editorFk
rowExcludeField: logExclude rowExcludeField: logExclude
@ -19,5 +22,7 @@ logs:
- itemTag - itemTag
- name: item - name: item
showField: name showField: name
logFields:
- size
exclude: exclude:
- image - image

View File

@ -2,6 +2,9 @@ const path = require('path');
const {loadConfig, toUpperCamelCase} = require('./util'); const {loadConfig, toUpperCamelCase} = require('./util');
const MultiMap = require('./multi-map'); const MultiMap = require('./multi-map');
/**
* Loads model configuration.
*/
module.exports = class ModelLoader { module.exports = class ModelLoader {
init(logger) { init(logger) {
const configDir = path.join(__dirname, '..'); const configDir = path.join(__dirname, '..');
@ -54,7 +57,7 @@ module.exports = class ModelLoader {
if (!tableInfo) { if (!tableInfo) {
tableInfo = { tableInfo = {
conf: tableConf, conf: tableConf,
log: logInfo logInfo
}; };
schemaMap.set(table.schema, table.name, tableInfo); schemaMap.set(table.schema, table.name, tableInfo);
} }
@ -85,10 +88,11 @@ module.exports = class ModelLoader {
? new RegExp(conf.excludeRegex) : null; ? new RegExp(conf.excludeRegex) : null;
const localProps = [ const localProps = [
'idName' 'idName',
'showField',
'logFields'
]; ];
const globalProps = [ const globalProps = [
'showField',
'userField', 'userField',
'rowExcludeField' 'rowExcludeField'
]; ];
@ -106,9 +110,10 @@ module.exports = class ModelLoader {
// Fetch columns & types // Fetch columns & types
const columns = new Set();
Object.assign (tableInfo, { Object.assign (tableInfo, {
castTypes: new Map(), castTypes: new Map(),
columns: new Map() columns
}); });
if (tableConf.types) if (tableConf.types)
@ -118,8 +123,7 @@ module.exports = class ModelLoader {
const [dbCols] = await db.query( const [dbCols] = await db.query(
`SELECT `SELECT
COLUMN_NAME \`col\`, COLUMN_NAME \`col\`,
DATA_TYPE \`type\`, DATA_TYPE \`type\`
COLUMN_DEFAULT \`def\`
FROM information_schema.\`COLUMNS\` FROM information_schema.\`COLUMNS\`
WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?`, WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?`,
[table, schema] [table, schema]
@ -128,14 +132,14 @@ module.exports = class ModelLoader {
const exclude = new Set(tableConf.exclude); const exclude = new Set(tableConf.exclude);
exclude.add(tableInfo.userField); exclude.add(tableInfo.userField);
for (const {col, type, def} of dbCols) { for (const {col, type} of dbCols) {
const isExcluded = const isExcluded =
excludeFields.has(col) excludeFields.has(col)
|| (excludeRegex && excludeRegex.test(col)) || (excludeRegex && excludeRegex.test(col))
|| exclude.has(col); || exclude.has(col);
if (!isExcluded) if (!isExcluded)
tableInfo.columns.set(col, {type, def}); columns.add(col);
const castType = conf.castTypes[type]; const castType = conf.castTypes[type];
if (castType && !tableInfo.castTypes.has(col)) if (castType && !tableInfo.castTypes.has(col))
@ -144,9 +148,10 @@ module.exports = class ModelLoader {
// Fetch primary key // Fetch primary key
if (!tableInfo.idName) { const {idName} = tableInfo;
const [dbPks] = await db.query( if (!idName) {
`SELECT COLUMN_NAME idName const [pks] = await db.query(
`SELECT COLUMN_NAME pk
FROM information_schema.KEY_COLUMN_USAGE FROM information_schema.KEY_COLUMN_USAGE
WHERE CONSTRAINT_NAME = 'PRIMARY' WHERE CONSTRAINT_NAME = 'PRIMARY'
AND TABLE_NAME = ? AND TABLE_NAME = ?
@ -154,38 +159,39 @@ module.exports = class ModelLoader {
[table, schema] [table, schema]
); );
if (!dbPks.length) if (!pks.length)
throw new Error(`Primary not found for table: ${schema}.${table}`); throw new Error(`Primary key not found: ${schema}.${table}`);
if (dbPks.length > 1) if (pks.length > 1)
throw new Error(`Only one column primary is supported: ${schema}.${table}`); throw new Error(`Only one column primary is supported: ${schema}.${table}`);
for (const {idName} of dbPks) const [{pk}] = pks;
tableInfo.idName = idName; tableInfo.idName = pk;
} } else if (!columns.has(idName))
throw new Error(`Primary column not found: ${schema}.${table}.${idName}`);
// Get show field // Get show field
const {showField} = tableInfo; const {showField} = tableInfo;
if (showField !== null && !tableInfo.isMain) { if (showField !== null) {
if (showField === undefined) { if (showField === undefined) {
if (!tableInfo.isMain || conf.logMainShowField)
for (const field of conf.showFields) { for (const field of conf.showFields) {
if (tableInfo.columns.has(field)) { if (columns.has(field)) {
tableInfo.showField = field; tableInfo.showField = field;
break; break;
} }
} }
} else { } else if (!columns.has(showField))
const match = showField.match(/(^.*)\$$/); throw new Error(`Show column not found: ${schema}.${table}.${showField}`);
if (match) tableInfo.showRelation = match[1];
}
} }
} }
// Fetch relation to main table
for (const [schema, table, tableInfo] of schemaMap) { for (const [schema, table, tableInfo] of schemaMap) {
// Fetch relation to main table
if (!tableInfo.conf.relation && !tableInfo.isMain) { if (!tableInfo.conf.relation && !tableInfo.isMain) {
const mainTable = tableInfo.log.mainTable; const mainTable = tableInfo.logInfo.mainTable;
const mainInfo = schemaMap.get(mainTable.schema, mainTable.name); const mainInfo = schemaMap.get(mainTable.schema, mainTable.name);
const [mainRelations] = await db.query( const [mainRelations] = await db.query(
@ -213,6 +219,16 @@ module.exports = class ModelLoader {
for (const {relation} of mainRelations) for (const {relation} of mainRelations)
tableInfo.relation = relation; tableInfo.relation = relation;
} }
// Set instance columns
const {columns} = tableInfo;
const cols = new Set(columns);
if (!conf.logId)
cols.delete(tableInfo.idName);
if (!conf.logRelation)
cols.delete(tableInfo.relation);
tableInfo.instanceColumns = [...cols.keys()];
} }
} }
} }

View File

@ -1,7 +1,7 @@
const MultiMap = require("./multi-map"); const MultiMap = require("./multi-map");
/** /**
* TODO: #5563 Fetch relations and show values in fronted * Caches and sets user friendly values for instaces and relations.
*/ */
module.exports = class ShowDb { module.exports = class ShowDb {
init(logger) { init(logger) {
@ -107,7 +107,7 @@ module.exports = class ShowDb {
if (save) tableInfo.showField = col; if (save) tableInfo.showField = col;
} }
// Clean tables and relations without required information // Remove tables without showable columns and prepare SELECT statements
for (const [schema, table] of relatedList) { for (const [schema, table] of relatedList) {
const tableInfo = tables.get(schema, table); const tableInfo = tables.get(schema, table);
@ -127,10 +127,26 @@ module.exports = class ShowDb {
WHERE ${sqlIdName} IN (?)`; WHERE ${sqlIdName} IN (?)`;
} }
for (const tableInfo of schemaMap.values()) // Remove relations without showable tables and set show configuration
for (const [col, relation] of tableInfo.relations) {
if (!tables.has(relation.schema, relation.table)) for (const [schema, table, tableInfo] of schemaMap) {
tableInfo.relations.delete(col); const {relations} = tableInfo;
for (const [col, relation] of relations) {
if (!tables.has(relation.schema, relation.table))
relations.delete(col);
}
const {showRelation} = tableInfo.conf;
if (showRelation) {
if (!relations.has(showRelation))
throw new Error(`Relation not found: ${schema}.${table}.${showRelation}`);
Object.assign(tableInfo, {
showField: showRelation +'$',
showRelation
});
}
} }
} }

View File

@ -313,10 +313,7 @@ module.exports = class MyLogger {
if (!tableInfo) return; if (!tableInfo) return;
const action = actions[eventName]; const action = actions[eventName];
const { const {rowExcludeField} = tableInfo;
columns,
rowExcludeField
} = tableInfo;
const changes = []; const changes = [];
function isExcluded(row) { function isExcluded(row) {
@ -354,6 +351,8 @@ module.exports = class MyLogger {
} }
if (action == 'update') { if (action == 'update') {
const cols = tableInfo.columns;
for (const row of evt.rows) { for (const row of evt.rows) {
const after = row.after; const after = row.after;
if (isExcluded(after)) continue; if (isExcluded(after)) continue;
@ -364,10 +363,9 @@ module.exports = class MyLogger {
let nColsChanged = 0; let nColsChanged = 0;
for (const col in before) { for (const col in before) {
if (columns.has(col) if (cols.has(col)
&& !equals(after[col], before[col])) { && !equals(after[col], before[col])) {
if (before[col] !== null) oldI[col] = castValue(col, before[col]);
oldI[col] = castValue(col, before[col]);
newI[col] = castValue(col, after[col]); newI[col] = castValue(col, after[col]);
nColsChanged++; nColsChanged++;
} }
@ -376,7 +374,7 @@ module.exports = class MyLogger {
changes.push({row: after, oldI, newI}); changes.push({row: after, oldI, newI});
} }
} else { } else {
const cols = columns.keys(); const cols = tableInfo.instanceColumns;
for (const row of evt.rows) { for (const row of evt.rows) {
if (isExcluded(row)) continue; if (isExcluded(row)) continue;
@ -474,13 +472,16 @@ module.exports = class MyLogger {
evt, evt,
changes changes
} = op; } = op;
const {
logInfo,
isMain,
relation,
modelName,
logFields
} = tableInfo;
const logInfo = tableInfo.log;
const isDelete = action == 'delete'; const isDelete = action == 'delete';
const isUpdate = action == 'update'; const isUpdate = action == 'update';
const isSecondary = !tableInfo.isMain;
const relation = tableInfo.relation;
const modelName = tableInfo.modelName;
const created = new Date(evt.timestamp); const created = new Date(evt.timestamp);
for (const change of changes) { for (const change of changes) {
@ -491,6 +492,11 @@ module.exports = class MyLogger {
case 'update': case 'update':
newI = change.newI; newI = change.newI;
oldI = change.oldI; oldI = change.oldI;
if (logFields) {
for (const field of logFields)
if (newI[field] === undefined)
newI[field] = row[field];
}
break; break;
case 'insert': case 'insert':
newI = change.instance; newI = change.instance;
@ -503,8 +509,8 @@ module.exports = class MyLogger {
const modelId = row[tableInfo.idName]; const modelId = row[tableInfo.idName];
const modelValue = change.modelValue ?? null; const modelValue = change.modelValue ?? null;
const oldInstance = oldI ? JSON.stringify(oldI) : null; const oldInstance = oldI ? JSON.stringify(oldI) : null;
const originFk = isSecondary ? row[relation] : modelId; const originFk = !isMain ? row[relation] : modelId;
const originChanged = isUpdate && isSecondary const originChanged = isUpdate && !isMain
&& newI[relation] !== undefined; && newI[relation] !== undefined;
let deleteRow; let deleteRow;

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "mylogger", "name": "mylogger",
"version": "1.0.0", "version": "1.1.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "mylogger", "name": "mylogger",
"version": "1.0.0", "version": "1.1.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"colors": "^1.4.0", "colors": "^1.4.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "mylogger", "name": "mylogger",
"version": "1.0.0", "version": "1.1.0",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "MySQL and MariaDB logger using binary log", "description": "MySQL and MariaDB logger using binary log",
"license": "GPL-3.0", "license": "GPL-3.0",