mirror of
https://github.com/FoxxMD/context-mod.git
synced 2026-04-19 03:00:07 -04:00
Logging improvements
* Insert activity identifier into logging labels after subreddit using dynamic labels * Simplify logger creation (don't need shuffle using improvements from above) * Add logging to Actions * Make check logging clearer and more succinct * Log more information on startup
This commit is contained in:
@@ -4,20 +4,25 @@ import {RemoveAction} from "./RemoveAction";
|
||||
import {ReportAction, ReportActionJson} from "./ReportAction";
|
||||
import {FlairAction, FlairActionJson} from "./SubmissionAction/FlairAction";
|
||||
import Action, {ActionJson} from "./index";
|
||||
import {Logger} from "winston";
|
||||
|
||||
export function actionFactory
|
||||
(config: ActionJson): Action {
|
||||
(config: ActionJson, logger: Logger): Action {
|
||||
let cfg;
|
||||
switch (config.kind) {
|
||||
case 'comment':
|
||||
return new CommentAction(config as CommentActionJson);
|
||||
cfg = config as CommentActionJson;
|
||||
return new CommentAction({...cfg, logger});
|
||||
case 'lock':
|
||||
return new LockAction();
|
||||
return new LockAction({logger});
|
||||
case 'remove':
|
||||
return new RemoveAction();
|
||||
return new RemoveAction({logger});
|
||||
case 'report':
|
||||
return new ReportAction(config as ReportActionJson);
|
||||
cfg = config as ReportActionJson;
|
||||
return new ReportAction({...cfg, logger});
|
||||
case 'flair':
|
||||
return new FlairAction(config as FlairActionJson);
|
||||
cfg = config as FlairActionJson;
|
||||
return new FlairAction({...cfg, logger});
|
||||
default:
|
||||
throw new Error('rule "kind" was not recognized.');
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import Action, {ActionJson, ActionConfig, ActionOptions} from "./index";
|
||||
import Snoowrap, {Comment} from "snoowrap";
|
||||
import Action, {ActionJson, ActionOptions} from "./index";
|
||||
import {Comment} from "snoowrap";
|
||||
import Submission from "snoowrap/dist/objects/Submission";
|
||||
import dayjs, {Dayjs} from "dayjs";
|
||||
import {renderContent} from "../Utils/SnoowrapUtils";
|
||||
import {RichContent} from "../Common/interfaces";
|
||||
import {RuleResult} from "../Rule";
|
||||
import LoggedError from "../Utils/LoggedError";
|
||||
|
||||
export const WIKI_DESCRIM = 'wiki:';
|
||||
|
||||
@@ -16,7 +17,6 @@ export class CommentAction extends Action {
|
||||
lock: boolean = false;
|
||||
sticky: boolean = false;
|
||||
distinguish: boolean = false;
|
||||
name?: string = 'Comment';
|
||||
|
||||
constructor(options: CommentActionOptions) {
|
||||
super(options);
|
||||
@@ -36,22 +36,30 @@ export class CommentAction extends Action {
|
||||
this.distinguish = distinguish;
|
||||
}
|
||||
|
||||
async handle(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
getKind() {
|
||||
return 'Comment';
|
||||
}
|
||||
|
||||
async process(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
if (this.hasWiki && (this.wikiFetched === undefined || Math.abs(dayjs().diff(this.wikiFetched, 'minute')) > 5)) {
|
||||
try {
|
||||
const wiki = item.subreddit.getWikiPage(this.wiki as string);
|
||||
this.content = await wiki.content_md;
|
||||
this.wikiFetched = dayjs();
|
||||
} catch (err) {
|
||||
this.logger.error(err);
|
||||
throw new Error(`Could not read wiki page. Please ensure the page '${this.wiki}' exists and is readable`);
|
||||
this.logger.error(`Could not read wiki page. Please ensure the page '${this.wiki}' exists and is readable`, err);
|
||||
throw new LoggedError(`Could not read wiki page. Please ensure the page '${this.wiki}' exists and is readable`);
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
const reply: Comment = await item.reply(renderContent(this.content, item, ruleResults));
|
||||
if (this.lock && item instanceof Submission) {
|
||||
// @ts-ignore
|
||||
await item.lock();
|
||||
if (this.lock) {
|
||||
if(item instanceof Submission) {
|
||||
// @ts-ignore
|
||||
await item.lock();
|
||||
} else {
|
||||
this.logger.warn('Snoowrap does not support locking Comments');
|
||||
}
|
||||
}
|
||||
if (this.distinguish) {
|
||||
// @ts-ignore
|
||||
|
||||
@@ -4,11 +4,16 @@ import Snoowrap, {Comment, Submission} from "snoowrap";
|
||||
import {RuleResult} from "../Rule";
|
||||
|
||||
export class LockAction extends Action {
|
||||
name?: string = 'Lock';
|
||||
async handle(item: Comment|Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
getKind() {
|
||||
return 'Lock';
|
||||
}
|
||||
|
||||
async process(item: Comment|Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
if (item instanceof Submission) {
|
||||
// @ts-ignore
|
||||
await item.lock();
|
||||
} else {
|
||||
this.logger.warn('Snoowrap does not support locking Comments');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ import Snoowrap, {Comment, Submission} from "snoowrap";
|
||||
import {RuleResult} from "../Rule";
|
||||
|
||||
export class RemoveAction extends Action {
|
||||
name?: string = 'Remove';
|
||||
async handle(item: Comment|Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
getKind() {
|
||||
return 'Remove';
|
||||
}
|
||||
|
||||
async process(item: Comment|Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
// @ts-ignore
|
||||
await item.remove();
|
||||
}
|
||||
|
||||
@@ -12,14 +12,17 @@ import {RuleResult} from "../Rule";
|
||||
|
||||
export class ReportAction extends Action {
|
||||
content: string;
|
||||
name?: string = 'Report';
|
||||
|
||||
constructor(options: ReportActionOptions) {
|
||||
super(options);
|
||||
this.content = options.content || '';
|
||||
}
|
||||
|
||||
async handle(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
getKind() {
|
||||
return 'Report';
|
||||
}
|
||||
|
||||
async process(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
const renderedContent = await renderContent(this.content, item, ruleResults);
|
||||
//const truncatedContent = reportTrunc(renderedContent);
|
||||
// @ts-ignore
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import {SubmissionActionConfig} from "./index";
|
||||
import Action, {ActionJson} from "../index";
|
||||
import Action, {ActionJson, ActionOptions} from "../index";
|
||||
import Snoowrap, {Comment, Submission} from "snoowrap";
|
||||
import {RuleResult} from "../../Rule";
|
||||
|
||||
export class FlairAction extends Action {
|
||||
text: string;
|
||||
css: string;
|
||||
name?: string = 'Flair';
|
||||
|
||||
constructor(options: FlairActionOptions) {
|
||||
super(options);
|
||||
@@ -17,10 +16,16 @@ export class FlairAction extends Action {
|
||||
this.css = options.css || '';
|
||||
}
|
||||
|
||||
async handle(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
getKind() {
|
||||
return 'Flair';
|
||||
}
|
||||
|
||||
async process(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
if (item instanceof Submission) {
|
||||
// @ts-ignore
|
||||
await item.assignFlair({text: this.text, cssClass: this.css})
|
||||
} else {
|
||||
this.logger.warn('Cannot flair Comment');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +34,7 @@ export class FlairAction extends Action {
|
||||
* @minProperties 1
|
||||
* @additionalProperties false
|
||||
* */
|
||||
export interface FlairActionOptions extends SubmissionActionConfig {
|
||||
export interface FlairActionConfig extends SubmissionActionConfig {
|
||||
/**
|
||||
* The text of the flair to apply
|
||||
* */
|
||||
@@ -40,9 +45,13 @@ export interface FlairActionOptions extends SubmissionActionConfig {
|
||||
css?: string,
|
||||
}
|
||||
|
||||
export interface FlairActionOptions extends FlairActionConfig,ActionOptions {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Flair the Submission
|
||||
* */
|
||||
export interface FlairActionJson extends FlairActionOptions, ActionJson {
|
||||
export interface FlairActionJson extends FlairActionConfig, ActionJson {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
import Snoowrap, {Comment, Submission} from "snoowrap";
|
||||
import {Comment, Submission} from "snoowrap";
|
||||
import {Logger} from "winston";
|
||||
import {createLabelledLogger, loggerMetaShuffle} from "../util";
|
||||
import {RuleResult} from "../Rule";
|
||||
|
||||
export abstract class Action {
|
||||
name?: string;
|
||||
logger: Logger;
|
||||
|
||||
constructor(options: ActionOptions = {}) {
|
||||
constructor(options: ActionOptions) {
|
||||
const {
|
||||
name,
|
||||
loggerPrefix = '',
|
||||
name = this.getKind(),
|
||||
logger,
|
||||
} = options;
|
||||
if (name !== undefined) {
|
||||
this.name = name;
|
||||
}
|
||||
if (logger === undefined) {
|
||||
const prefix = `${loggerPrefix}|${this.name}`;
|
||||
this.logger = createLabelledLogger(prefix, prefix);
|
||||
} else {
|
||||
this.logger = logger.child(loggerMetaShuffle(logger, name || 'Action', undefined, {truncateLength: 100}));
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
const uniqueName = this.name === this.getKind() ? this.getKind() : `${this.getKind()} - ${this.name}`;
|
||||
this.logger = logger.child({labels: ['Action', uniqueName]});
|
||||
}
|
||||
|
||||
abstract handle(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void>;
|
||||
abstract getKind(): string;
|
||||
|
||||
async handle(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void> {
|
||||
await this.process(item, ruleResults);
|
||||
this.logger.debug('Done');
|
||||
}
|
||||
|
||||
abstract process(item: Comment | Submission, ruleResults: RuleResult[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface ActionOptions {
|
||||
name?: string;
|
||||
logger?: Logger,
|
||||
loggerPrefix?: string,
|
||||
logger: Logger,
|
||||
}
|
||||
|
||||
export interface ActionConfig {
|
||||
|
||||
18
src/App.ts
18
src/App.ts
@@ -1,7 +1,7 @@
|
||||
import Snoowrap from "snoowrap";
|
||||
import {Manager} from "./Subreddit/Manager";
|
||||
import winston, {Logger} from "winston";
|
||||
import {labelledFormat, loggerMetaShuffle} from "./util";
|
||||
import {labelledFormat} from "./util";
|
||||
import snoowrap from "snoowrap";
|
||||
import pEvent from "p-event";
|
||||
import EventEmitter from "events";
|
||||
@@ -100,31 +100,36 @@ export class App {
|
||||
maxRetryAttempts: 5,
|
||||
debug: shouldDebug,
|
||||
// @ts-ignore
|
||||
logger: this.logger.child(loggerMetaShuffle(this.logger, undefined, ['Snoowrap'])),
|
||||
logger: this.logger.child({labels: ['Snoowrap']}),
|
||||
continueAfterRatelimitError: true,
|
||||
});
|
||||
}
|
||||
|
||||
async buildManagers(subreddits: string[] = []) {
|
||||
let availSubs = [];
|
||||
const name = await this.client.getMe().name;
|
||||
this.logger.info(`Authenticated Account: /u/${name}`);
|
||||
for (const sub of await this.client.getModeratedSubreddits()) {
|
||||
// TODO don't know a way to check permissions yet
|
||||
availSubs.push(sub);
|
||||
}
|
||||
this.logger.info(`/u/${name} is a moderator of these subreddits: ${availSubs.map(x => x.display_name).join(', ')}`);
|
||||
|
||||
let subsToRun = [];
|
||||
const subsToUse = subreddits.length > 0 ? subreddits : this.subreddits;
|
||||
if (subsToUse.length > 0) {
|
||||
this.logger.info(`User-defined subreddit constraints detected (CLI argument or environmental variable), will try to run on: ${subsToUse.join(', ')}`);
|
||||
for (const sub of subsToUse) {
|
||||
const asub = availSubs.find(x => x.display_name.toLowerCase() === sub.trim().toLowerCase())
|
||||
if (asub === undefined) {
|
||||
this.logger.error(`Will not run on ${sub} because is not modded by, or does not have appropriate permissions to mod with, for this client.`);
|
||||
this.logger.warn(`Will not run on ${sub} because is not modded by, or does not have appropriate permissions to mod with, for this client.`);
|
||||
} else {
|
||||
subsToRun.push(asub);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// otherwise assume all moddable subs from client should be run on
|
||||
this.logger.info('No user-defined subreddit constraints detected, will try to run on all');
|
||||
subsToRun = availSubs;
|
||||
}
|
||||
|
||||
@@ -137,21 +142,20 @@ export class App {
|
||||
const wiki = sub.getWikiPage(this.wikiLocation);
|
||||
content = await wiki.content_md;
|
||||
} catch (err) {
|
||||
this.logger.error(`Could not read wiki configuration for ${sub.display_name}. Please ensure the page 'contextbot' exists and is readable -- error: ${err.message}`);
|
||||
this.logger.error(`[${sub.display_name}] Could not read wiki configuration. Please ensure the page 'contextbot' exists and is readable -- error: ${err.message}`);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
json = JSON.parse(content);
|
||||
|
||||
} catch (err) {
|
||||
this.logger.error(`Wiki page contents for ${sub.display_name} was not valid -- error: ${err.message}`);
|
||||
this.logger.error(`[${sub.display_name}] Wiki page contents was not valid -- error: ${err.message}`);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
subSchedule.push(new Manager(sub, this.client, this.logger, json));
|
||||
} catch (err) {
|
||||
debugger;
|
||||
this.logger.error(`Config for ${sub.display_name} was not valid, will not run for this subreddit`, undefined, err);
|
||||
this.logger.error(`[${sub.display_name}] Config was not valid`, undefined, err);
|
||||
}
|
||||
}
|
||||
this.subManagers = subSchedule;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {Logger} from "winston";
|
||||
import {Comment, Submission} from "snoowrap";
|
||||
import {actionFactory} from "../Action/ActionFactory";
|
||||
import {ruleFactory} from "../Rule/RuleFactory";
|
||||
import {createLabelledLogger, loggerMetaShuffle, mergeArr} from "../util";;
|
||||
import {mergeArr, ruleNamesFromResults} from "../util";
|
||||
import {JoinCondition, JoinOperands} from "../Common/interfaces";
|
||||
import * as RuleSchema from '../Schema/Rule.json';
|
||||
import * as RuleSetSchema from '../Schema/RuleSet.json';
|
||||
@@ -32,12 +32,7 @@ export class Check implements ICheck {
|
||||
actions = [],
|
||||
} = options;
|
||||
|
||||
if (options.logger !== undefined) {
|
||||
// @ts-ignore
|
||||
this.logger = options.logger.child(loggerMetaShuffle(options.logger, undefined, [`CHK ${name}`]), mergeArr);
|
||||
} else {
|
||||
this.logger = createLabelledLogger('Check');
|
||||
}
|
||||
this.logger = options.logger.child({labels: [`Check ${name}`]}, mergeArr);
|
||||
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
@@ -50,16 +45,13 @@ export class Check implements ICheck {
|
||||
let setErrors: any = [];
|
||||
let ruleErrors: any = [];
|
||||
if (valid) {
|
||||
// @ts-ignore
|
||||
r.logger = this.logger;
|
||||
this.rules.push(new RuleSet(r as RuleSetObjectJson));
|
||||
const ruleConfig = r as RuleSetObjectJson;
|
||||
this.rules.push(new RuleSet({...ruleConfig, logger: this.logger}));
|
||||
} else {
|
||||
setErrors = ajv.errors;
|
||||
valid = ajv.validate(RuleSchema, r);
|
||||
if (valid) {
|
||||
// @ts-ignore
|
||||
r.logger = this.logger;
|
||||
this.rules.push(ruleFactory(r as RuleJSONConfig));
|
||||
this.rules.push(ruleFactory(r as RuleJSONConfig, this.logger));
|
||||
} else {
|
||||
ruleErrors = ajv.errors;
|
||||
const leastErrorType = setErrors.length < ruleErrors ? 'RuleSet' : 'Rule';
|
||||
@@ -79,7 +71,8 @@ export class Check implements ICheck {
|
||||
} else {
|
||||
let valid = ajv.validate(ActionSchema, a);
|
||||
if (valid) {
|
||||
this.actions.push(actionFactory(a as ActionJson));
|
||||
const aj = a as ActionJson;
|
||||
this.actions.push(actionFactory(aj, this.logger));
|
||||
// @ts-ignore
|
||||
a.logger = this.logger;
|
||||
} else {
|
||||
@@ -92,13 +85,11 @@ export class Check implements ICheck {
|
||||
}
|
||||
|
||||
async run(item: Submission | Comment, existingResults: RuleResult[] = []): Promise<[boolean, RuleResult[]]> {
|
||||
//this.logger.debug('Starting check');
|
||||
let allResults: RuleResult[] = [];
|
||||
let runOne = false;
|
||||
for (const r of this.rules) {
|
||||
const combinedResults = [...existingResults, ...allResults];
|
||||
const [passed, results] = await r.run(item, combinedResults);
|
||||
//allResults = allResults.concat(determineNewResults(combinedResults, results));
|
||||
allResults = allResults.concat(results);
|
||||
if (passed === null) {
|
||||
continue;
|
||||
@@ -106,22 +97,28 @@ export class Check implements ICheck {
|
||||
runOne = true;
|
||||
if (passed) {
|
||||
if (this.condition === 'OR') {
|
||||
this.logger.info(`✔️ => Rules (OR): ${ruleNamesFromResults(allResults)}`);
|
||||
return [true, allResults];
|
||||
}
|
||||
} else if (this.condition === 'AND') {
|
||||
this.logger.info(`❌ => Rules (AND): ${ruleNamesFromResults(allResults)}`);
|
||||
return [false, allResults];
|
||||
}
|
||||
}
|
||||
if (!runOne) {
|
||||
this.logger.info('❌ => All Rules skipped because of Author checks');
|
||||
return [false, allResults];
|
||||
}
|
||||
this.logger.info(`✔️ => Rules (AND) : ${ruleNamesFromResults(allResults)}`);
|
||||
return [true, allResults];
|
||||
}
|
||||
|
||||
async runActions(item: Submission | Comment, ruleResults: RuleResult[]): Promise<void> {
|
||||
this.logger.debug('Running Actions');
|
||||
for (const a of this.actions) {
|
||||
await a.handle(item, ruleResults);
|
||||
}
|
||||
this.logger.info('Ran Actions');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +137,7 @@ export interface ICheck extends JoinCondition {
|
||||
export interface CheckOptions extends ICheck {
|
||||
rules: Array<IRuleSet | IRule>
|
||||
actions: ActionConfig[]
|
||||
logger?: Logger
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
export interface CheckJson extends ICheck {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Logger} from "winston";
|
||||
import {createLabelledLogger, loggerMetaShuffle, mergeArr} from "./util";
|
||||
import {mergeArr} from "./util";
|
||||
import {CommentCheck} from "./Check/CommentCheck";
|
||||
import {SubmissionCheck} from "./Check/SubmissionCheck";
|
||||
|
||||
@@ -17,19 +17,17 @@ import {isActionJson} from "./Action";
|
||||
const ajv = new Ajv();
|
||||
|
||||
export interface ConfigBuilderOptions {
|
||||
logger?: Logger,
|
||||
logger: Logger,
|
||||
}
|
||||
|
||||
export class ConfigBuilder {
|
||||
configLogger: Logger;
|
||||
logger: Logger;
|
||||
|
||||
constructor(options: ConfigBuilderOptions) {
|
||||
|
||||
if (options.logger !== undefined) {
|
||||
this.logger = options.logger.child(loggerMetaShuffle(options.logger, 'Config'), mergeArr);
|
||||
} else {
|
||||
this.logger = createLabelledLogger(`Config`, `Config`);
|
||||
}
|
||||
this.configLogger = options.logger.child({leaf: 'Config'}, mergeArr);
|
||||
this.logger = options.logger;
|
||||
}
|
||||
|
||||
buildFromJson(config: object): [Array<SubmissionCheck>,Array<CommentCheck>,ManagerOptions] {
|
||||
@@ -66,7 +64,7 @@ export class ConfigBuilder {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.logger.error('Json config was not valid. Please use schema to check validity.');
|
||||
this.configLogger.error('Json config was not valid. Please use schema to check validity.');
|
||||
if(Array.isArray(ajv.errors)) {
|
||||
for(const err of ajv.errors) {
|
||||
let suffix = '';
|
||||
@@ -76,7 +74,7 @@ export class ConfigBuilder {
|
||||
suffix = err.params.allowedValues.join(', ');
|
||||
suffix = ` [${suffix}]`;
|
||||
}
|
||||
this.logger.error(`${err.keyword}: ${err.schemaPath} => ${err.message}${suffix}`);
|
||||
this.configLogger.error(`${err.keyword}: ${err.schemaPath} => ${err.message}${suffix}`);
|
||||
}
|
||||
}
|
||||
throw new LoggedError();
|
||||
|
||||
@@ -3,18 +3,24 @@ import RepeatActivityRule, {RepeatActivityJSONConfig} from "./SubmissionRule/Rep
|
||||
import {Rule, RuleJSONConfig} from "./index";
|
||||
import AuthorRule, {AuthorRuleJSONConfig} from "./AuthorRule";
|
||||
import {AttributionJSONConfig, AttributionRule} from "./SubmissionRule/AttributionRule";
|
||||
import {Logger} from "winston";
|
||||
|
||||
export function ruleFactory
|
||||
(config: RuleJSONConfig): Rule {
|
||||
(config: RuleJSONConfig, logger: Logger): Rule {
|
||||
let cfg;
|
||||
switch (config.kind) {
|
||||
case 'recentActivity':
|
||||
return new RecentActivityRule(config as RecentActivityRuleJSONConfig);
|
||||
cfg = config as RecentActivityRuleJSONConfig;
|
||||
return new RecentActivityRule({...cfg, logger});
|
||||
case 'repeatActivity':
|
||||
return new RepeatActivityRule(config as RepeatActivityJSONConfig);
|
||||
cfg = config as RepeatActivityJSONConfig;
|
||||
return new RepeatActivityRule({...cfg, logger});
|
||||
case 'author':
|
||||
return new AuthorRule(config as AuthorRuleJSONConfig);
|
||||
cfg = config as AuthorRuleJSONConfig;
|
||||
return new AuthorRule({...cfg, logger});
|
||||
case 'attribution':
|
||||
return new AttributionRule(config as AttributionJSONConfig);
|
||||
cfg = config as AttributionJSONConfig;
|
||||
return new AttributionRule({...cfg, logger});
|
||||
default:
|
||||
throw new Error('rule "kind" was not recognized.');
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {IRule, Triggerable, Rule, RuleJSONConfig, RuleResult} from "./index";
|
||||
import {Comment, Submission} from "snoowrap";
|
||||
import {ruleFactory} from "./RuleFactory";
|
||||
import {createLabelledLogger, loggerMetaShuffle} from "../util";
|
||||
import {mergeArr} from "../util";
|
||||
import {Logger} from "winston";
|
||||
import {JoinCondition, JoinOperands} from "../Common/interfaces";
|
||||
import * as RuleSchema from '../Schema/Rule.json';
|
||||
@@ -17,11 +17,7 @@ export class RuleSet implements IRuleSet, Triggerable {
|
||||
|
||||
constructor(options: RuleSetOptions) {
|
||||
const {logger, condition = 'AND', rules = []} = options;
|
||||
if (logger !== undefined) {
|
||||
this.logger = logger.child(loggerMetaShuffle(logger, 'Rule Set'));
|
||||
} else {
|
||||
this.logger = createLabelledLogger('Rule Set');
|
||||
}
|
||||
this.logger = logger.child({leaf: 'Rule Set'}, mergeArr);
|
||||
this.condition = condition;
|
||||
for (const r of rules) {
|
||||
if (r instanceof Rule) {
|
||||
@@ -29,9 +25,7 @@ export class RuleSet implements IRuleSet, Triggerable {
|
||||
} else {
|
||||
const valid = ajv.validate(RuleSchema, r);
|
||||
if (valid) {
|
||||
// @ts-ignore
|
||||
r.logger = this.logger;
|
||||
this.rules.push(ruleFactory(r as RuleJSONConfig));
|
||||
this.rules.push(ruleFactory(r as RuleJSONConfig, logger));
|
||||
} else {
|
||||
this.logger.warn('Could not build rule because of JSON errors', {}, {errors: ajv.errors, obj: r});
|
||||
}
|
||||
@@ -77,7 +71,7 @@ export interface IRuleSet extends JoinCondition {
|
||||
|
||||
export interface RuleSetOptions extends IRuleSet {
|
||||
rules: Array<IRule | RuleJSONConfig>,
|
||||
logger?: Logger
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import {Comment, RedditUser} from "snoowrap";
|
||||
import {Comment} from "snoowrap";
|
||||
import Submission from "snoowrap/dist/objects/Submission";
|
||||
import {Logger} from "winston";
|
||||
import {createLabelledLogger, findResultByPremise, loggerMetaShuffle, mergeArr} from "../util";
|
||||
import {findResultByPremise, mergeArr} from "../util";
|
||||
import {testAuthorCriteria} from "../Utils/SnoowrapUtils";
|
||||
|
||||
export interface RuleOptions {
|
||||
name?: string;
|
||||
authors?: AuthorOptions;
|
||||
logger?: Logger
|
||||
loggerPrefix?: string
|
||||
logger: Logger
|
||||
}
|
||||
|
||||
export interface RulePremise {
|
||||
@@ -39,7 +38,6 @@ export abstract class Rule implements IRule, Triggerable {
|
||||
constructor(options: RuleOptions) {
|
||||
const {
|
||||
name = this.getKind(),
|
||||
loggerPrefix = '',
|
||||
logger,
|
||||
authors: {
|
||||
include = [],
|
||||
@@ -54,20 +52,13 @@ export abstract class Rule implements IRule, Triggerable {
|
||||
}
|
||||
|
||||
const ruleUniqueName = this.name === undefined ? this.getKind() : `${this.getKind()} - ${this.name}`;
|
||||
if (logger === undefined) {
|
||||
const prefix = `${loggerPrefix}|${ruleUniqueName}`;
|
||||
this.logger = createLabelledLogger(prefix, prefix);
|
||||
} else {
|
||||
this.logger = logger.child(loggerMetaShuffle(logger, undefined, [ruleUniqueName], {truncateLength: 100}));
|
||||
}
|
||||
this.logger = logger.child({labels: ['Rule',`${ruleUniqueName}`]}, mergeArr);
|
||||
}
|
||||
|
||||
async run(item: Comment | Submission, existingResults: RuleResult[] = []): Promise<[(boolean | null), RuleResult[]]> {
|
||||
this.logger = this.logger.child(loggerMetaShuffle(this.logger, `${item instanceof Submission ? 'SUB' : 'COMM'} ${item.id}`), mergeArr);
|
||||
this.logger.debug('Starting');
|
||||
const existingResult = findResultByPremise(this.getPremise(), existingResults);
|
||||
if (existingResult) {
|
||||
this.logger.debug('Returning existing result');
|
||||
this.logger.debug(`Returning existing result of ${existingResult.triggered ? '✔️' : '❌'}`);
|
||||
return Promise.resolve([existingResult.triggered, [{...existingResult, name: this.name}]]);
|
||||
}
|
||||
if (this.authors.include !== undefined && this.authors.include.length > 0) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {SubmissionCheck} from "../Check/SubmissionCheck";
|
||||
import {CommentCheck} from "../Check/CommentCheck";
|
||||
import {
|
||||
determineNewResults,
|
||||
loggerMetaShuffle,
|
||||
mergeArr,
|
||||
} from "../util";
|
||||
import {CommentStream, SubmissionStream} from "snoostorm";
|
||||
@@ -32,8 +31,23 @@ export class Manager {
|
||||
lastHeartbeat = dayjs();
|
||||
apiLimitWarning: number;
|
||||
|
||||
displayLabel: string;
|
||||
currentLabels?: string[];
|
||||
|
||||
getCurrentLabels = () => {
|
||||
return this.currentLabels;
|
||||
}
|
||||
|
||||
constructor(sub: Subreddit, client: Snoowrap, logger: Logger, sourceData: object, opts: ManagerOptions = {}) {
|
||||
this.logger = logger.child(loggerMetaShuffle(logger, undefined, [`r/${sub.display_name}`], {truncateLength: 40}), mergeArr);
|
||||
const displayLabel = `r/${sub.display_name}`;
|
||||
this.displayLabel = displayLabel;
|
||||
this.currentLabels = [displayLabel];
|
||||
const getLabels = this.getCurrentLabels;
|
||||
// dynamic default meta for winston feasible using function getters
|
||||
// https://github.com/winstonjs/winston/issues/1626#issuecomment-531142958
|
||||
this.logger = logger.child({
|
||||
get labels () { return getLabels() }
|
||||
}, mergeArr);
|
||||
|
||||
const configBuilder = new ConfigBuilder({logger: this.logger});
|
||||
const [subChecks, commentChecks, configManagerOptions] = configBuilder.buildFromJson(sourceData);
|
||||
@@ -64,15 +78,15 @@ export class Manager {
|
||||
const itemId = await item.id;
|
||||
let allRuleResults: RuleResult[] = [];
|
||||
const itemIdentifier = `${checkType} ${itemId}`;
|
||||
this.currentLabels = [this.displayLabel, itemIdentifier];
|
||||
const [peek, _] = await itemContentPeek(item);
|
||||
this.logger.info(`New Event: ${itemIdentifier} => ${peek}`);
|
||||
this.logger.info(`<EVENT> ${peek}`);
|
||||
|
||||
for (const check of checks) {
|
||||
if(checkNames.length > 0 && !checkNames.map(x => x.toLowerCase()).some(x => x === check.name.toLowerCase())) {
|
||||
this.logger.debug(`Check ${check} not in array of requested checks to run, skipping`);
|
||||
continue;
|
||||
}
|
||||
this.logger.debug(`[${itemIdentifier}] Running Check ${check.name}`);
|
||||
let triggered = false;
|
||||
let currentResults: RuleResult[] = [];
|
||||
try {
|
||||
@@ -80,22 +94,12 @@ export class Manager {
|
||||
currentResults = checkResults;
|
||||
allRuleResults = allRuleResults.concat(determineNewResults(allRuleResults, checkResults));
|
||||
triggered = checkTriggered;
|
||||
const invokedRules = checkResults.map(x => x.name || x.premise.kind).join(' | ');
|
||||
if (checkTriggered) {
|
||||
this.logger.info(`[${itemIdentifier}] [CHK ${check.name}] Triggered with invoked Rules: ${invokedRules}`);
|
||||
} else {
|
||||
this.logger.debug(`[${itemIdentifier}] [CHK ${check.name}] WAS NOT triggered with invoked Rule(s): ${invokedRules}`);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
this.logger.warn(`[${itemIdentifier}] [CHK ${check.name}] Failed with error: ${e.message}`, e);
|
||||
this.logger.warn(`[Check ${check.name}] Failed with error: ${e.message}`, e);
|
||||
}
|
||||
|
||||
if (triggered) {
|
||||
this.logger.debug(`[${itemIdentifier}] [CHK ${check.name}] Running actions`);
|
||||
// TODO give actions a name
|
||||
await check.runActions(item, currentResults);
|
||||
this.logger.info(`[${itemIdentifier}] [CHK ${check.name}] Ran actions`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -129,8 +133,6 @@ export class Manager {
|
||||
|
||||
this.streamSub.once('listing', async (listing) => {
|
||||
this.subListedOnce = true;
|
||||
// for debugging
|
||||
// await this.runChecks('Submission', listing[0]);
|
||||
});
|
||||
this.streamSub.on('item', async (item) => {
|
||||
if (!this.subListedOnce) {
|
||||
@@ -164,11 +166,16 @@ export class Manager {
|
||||
}
|
||||
|
||||
if (this.streamSub !== undefined) {
|
||||
this.logger.info('Bot Running');
|
||||
await pEvent(this.streamSub, 'end');
|
||||
} else if (this.streamComments !== undefined) {
|
||||
this.logger.info('Bot Running');
|
||||
await pEvent(this.streamComments, 'end');
|
||||
} else {
|
||||
this.logger.warn('No submission or comment checks to run!');
|
||||
this.logger.warn('No submission or comment checks to run! Bot will not run.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('Bot Stopped');
|
||||
}
|
||||
}
|
||||
|
||||
28
src/util.ts
28
src/util.ts
@@ -19,15 +19,6 @@ const CWD = process.cwd();
|
||||
|
||||
export const truncateStringToLength = (length: number, truncStr = '...') => (str: string) => str.length > length ? `${str.slice(0, length - truncStr.length - 1)}${truncStr}` : str;
|
||||
|
||||
export const loggerMetaShuffle = (logger: Logger, newLeaf: (string | undefined | null) = null, extraLabels: string[] = [], {truncateLength = 50} = {}) => {
|
||||
const labelTrunc = truncateStringToLength(truncateLength);
|
||||
const {labels = [], leaf} = logger.defaultMeta || {};
|
||||
return {
|
||||
labels: labels.concat(extraLabels.map(x => labelTrunc(x))),
|
||||
leaf: newLeaf
|
||||
};
|
||||
}
|
||||
|
||||
let longestLabel = 3;
|
||||
// @ts-ignore
|
||||
export const defaultFormat = printf(({
|
||||
@@ -62,7 +53,7 @@ export const defaultFormat = printf(({
|
||||
let labelContent = `[${label.padEnd(longestLabel)}]`;
|
||||
if (labels.length > 0 || (leaf !== null && leaf !== undefined)) {
|
||||
let nodes = labels;
|
||||
if (leaf !== null) {
|
||||
if (leaf !== null && leaf !== undefined) {
|
||||
nodes.push(leaf);
|
||||
}
|
||||
//labelContent = `${labels.slice(0, labels.length).map((x: string) => `[${x}]`).join(' ')}`
|
||||
@@ -89,19 +80,6 @@ export const labelledFormat = (labelName = 'App') => {
|
||||
);
|
||||
}
|
||||
|
||||
export const createLabelledLogger = (name = 'default', label = 'App') => {
|
||||
if (winston.loggers.has(name)) {
|
||||
return winston.loggers.get(name);
|
||||
}
|
||||
const def = winston.loggers.get('default');
|
||||
winston.loggers.add(name, {
|
||||
transports: def.transports,
|
||||
level: def.level,
|
||||
format: labelledFormat(label)
|
||||
});
|
||||
return winston.loggers.get(name);
|
||||
}
|
||||
|
||||
export interface groupByOptions {
|
||||
lowercase?: boolean
|
||||
}
|
||||
@@ -212,6 +190,10 @@ export const mergeArr = (objValue: [], srcValue: []): (any[] | undefined) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const ruleNamesFromResults = (results: RuleResult[]) => {
|
||||
return results.map(x => x.name || x.premise.kind).join(' | ')
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user