feat(logs): enhance the logs and add a prefix with the issue number (#222)

* feat(logs): enhance the logs and add a prefix with the issue number

* chore: use camelCase for constants

use the new logger for the new code due to the rebase
This commit is contained in:
Geoffrey Testelin 2021-01-16 15:49:50 +01:00 committed by GitHub
parent 552e4c60f0
commit e6b77bc964
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 273 additions and 48 deletions

View File

@ -1,9 +1,10 @@
import * as core from '@actions/core';
import {context, getOctokit} from '@actions/github'; import {context, getOctokit} from '@actions/github';
import {GitHub} from '@actions/github/lib/utils'; import {GitHub} from '@actions/github/lib/utils';
import {GetResponseTypeFromEndpointMethod} from '@octokit/types'; import {GetResponseTypeFromEndpointMethod} from '@octokit/types';
import {IssueType} from './enums/issue-type.enum'; import {IssueType} from './enums/issue-type.enum';
import {getIssueType} from './functions/get-issue-type'; import {getIssueType} from './functions/get-issue-type';
import {IssueLogger} from './classes/issue-logger';
import {Logger} from './classes/logger';
import {isLabeled} from './functions/is-labeled'; import {isLabeled} from './functions/is-labeled';
import {isPullRequest} from './functions/is-pull-request'; import {isPullRequest} from './functions/is-pull-request';
import {labelsToList} from './functions/labels-to-list'; import {labelsToList} from './functions/labels-to-list';
@ -73,6 +74,8 @@ export interface IssueProcessorOptions {
deleteBranch: boolean; deleteBranch: boolean;
} }
const logger: Logger = new Logger();
/*** /***
* Handle processing of issues for staleness/closure. * Handle processing of issues for staleness/closure.
*/ */
@ -127,7 +130,7 @@ export class IssueProcessor {
} }
if (this.options.debugOnly) { if (this.options.debugOnly) {
core.warning( logger.warning(
'Executing in debug mode. Debug output will be written but no issues will be processed.' 'Executing in debug mode. Debug output will be written but no issues will be processed.'
); );
} }
@ -141,14 +144,16 @@ export class IssueProcessor {
const actor: string = await this.getActor(); const actor: string = await this.getActor();
if (issues.length <= 0) { if (issues.length <= 0) {
core.info('No more issues found to process. Exiting.'); logger.info('---');
logger.info('No more issues found to process. Exiting.');
return this.operationsLeft; return this.operationsLeft;
} }
for (const issue of issues.values()) { for (const issue of issues.values()) {
const issueLogger: IssueLogger = new IssueLogger(issue);
const isPr = isPullRequest(issue); const isPr = isPullRequest(issue);
core.info( issueLogger.info(
`Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})` `Found issue: issue #${issue.number} last updated ${issue.updated_at} (is pr? ${isPr})`
); );
@ -177,25 +182,25 @@ export class IssueProcessor {
: this._getDaysBeforeIssueStale(); : this._getDaysBeforeIssueStale();
if (isPr) { if (isPr) {
core.info(`Days before pull request stale: ${daysBeforeStale}`); issueLogger.info(`Days before pull request stale: ${daysBeforeStale}`);
} else { } else {
core.info(`Days before issue stale: ${daysBeforeStale}`); issueLogger.info(`Days before issue stale: ${daysBeforeStale}`);
} }
const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale); const shouldMarkAsStale: boolean = shouldMarkWhenStale(daysBeforeStale);
if (!staleMessage && shouldMarkAsStale) { if (!staleMessage && shouldMarkAsStale) {
core.info(`Skipping ${issueType} due to empty stale message`); issueLogger.info(`Skipping ${issueType} due to empty stale message`);
continue; continue;
} }
if (issue.state === 'closed') { if (issue.state === 'closed') {
core.info(`Skipping ${issueType} because it is closed`); issueLogger.info(`Skipping ${issueType} because it is closed`);
continue; // don't process closed issues continue; // don't process closed issues
} }
if (issue.locked) { if (issue.locked) {
core.info(`Skipping ${issueType} because it is locked`); issueLogger.info(`Skipping ${issueType} because it is locked`);
continue; // don't process locked issues continue; // don't process locked issues
} }
@ -204,7 +209,9 @@ export class IssueProcessor {
isLabeled(issue, exemptLabel) isLabeled(issue, exemptLabel)
) )
) { ) {
core.info(`Skipping ${issueType} because it has an exempt label`); issueLogger.info(
`Skipping ${issueType} because it has an exempt label`
);
continue; // don't process exempt issues continue; // don't process exempt issues
} }
@ -212,9 +219,9 @@ export class IssueProcessor {
let isStale: boolean = isLabeled(issue, staleLabel); let isStale: boolean = isLabeled(issue, staleLabel);
if (isStale) { if (isStale) {
core.info(`This issue has a stale label`); issueLogger.info(`This issue has a stale label`);
} else { } else {
core.info(`This issue hasn't a stale label`); issueLogger.info(`This issue hasn't a stale label`);
} }
// should this issue be marked stale? // should this issue be marked stale?
@ -225,7 +232,7 @@ export class IssueProcessor {
// determine if this issue needs to be marked stale first // determine if this issue needs to be marked stale first
if (!isStale && shouldBeStale && shouldMarkAsStale) { if (!isStale && shouldBeStale && shouldMarkAsStale) {
core.info( issueLogger.info(
`Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label` `Marking ${issueType} stale because it was last updated on ${issue.updated_at} and it does not have a stale label`
); );
await this.markStale(issue, staleMessage, staleLabel, skipMessage); await this.markStale(issue, staleMessage, staleLabel, skipMessage);
@ -234,7 +241,7 @@ export class IssueProcessor {
// process the issue if it was marked stale // process the issue if it was marked stale
if (isStale) { if (isStale) {
core.info(`Found a stale ${issueType}`); issueLogger.info(`Found a stale ${issueType}`);
await this.processStaleIssue( await this.processStaleIssue(
issue, issue,
issueType, issueType,
@ -247,7 +254,7 @@ export class IssueProcessor {
} }
if (this.operationsLeft <= 0) { if (this.operationsLeft <= 0) {
core.warning('Reached max number of operations to process. Exiting.'); logger.warning('Reached max number of operations to process. Exiting.');
return 0; return 0;
} }
@ -264,16 +271,19 @@ export class IssueProcessor {
closeMessage?: string, closeMessage?: string,
closeLabel?: string closeLabel?: string
) { ) {
const issueLogger: IssueLogger = new IssueLogger(issue);
const markedStaleOn: string = const markedStaleOn: string =
(await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at; (await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at;
core.info(`Issue #${issue.number} marked stale on: ${markedStaleOn}`); issueLogger.info(
`Issue #${issue.number} marked stale on: ${markedStaleOn}`
);
const issueHasComments: boolean = await this.hasCommentsSince( const issueHasComments: boolean = await this.hasCommentsSince(
issue, issue,
markedStaleOn, markedStaleOn,
actor actor
); );
core.info( issueLogger.info(
`Issue #${issue.number} has been commented on: ${issueHasComments}` `Issue #${issue.number} has been commented on: ${issueHasComments}`
); );
@ -283,20 +293,22 @@ export class IssueProcessor {
: this._getDaysBeforeIssueClose(); : this._getDaysBeforeIssueClose();
if (isPr) { if (isPr) {
core.info(`Days before pull request close: ${daysBeforeClose}`); issueLogger.info(`Days before pull request close: ${daysBeforeClose}`);
} else { } else {
core.info(`Days before issue close: ${daysBeforeClose}`); issueLogger.info(`Days before issue close: ${daysBeforeClose}`);
} }
const issueHasUpdate: boolean = IssueProcessor.updatedSince( const issueHasUpdate: boolean = IssueProcessor.updatedSince(
issue.updated_at, issue.updated_at,
daysBeforeClose daysBeforeClose
); );
core.info(`Issue #${issue.number} has been updated: ${issueHasUpdate}`); issueLogger.info(
`Issue #${issue.number} has been updated: ${issueHasUpdate}`
);
// should we un-stale this issue? // should we un-stale this issue?
if (this.options.removeStaleWhenUpdated && issueHasComments) { if (this.options.removeStaleWhenUpdated && issueHasComments) {
core.info( issueLogger.info(
`Issue #${issue.number} is no longer stale. Removing stale label.` `Issue #${issue.number} is no longer stale. Removing stale label.`
); );
await this.removeLabel(issue, staleLabel); await this.removeLabel(issue, staleLabel);
@ -308,20 +320,20 @@ export class IssueProcessor {
} }
if (!issueHasComments && !issueHasUpdate) { if (!issueHasComments && !issueHasUpdate) {
core.info( issueLogger.info(
`Closing ${issueType} because it was last updated on ${issue.updated_at}` `Closing ${issueType} because it was last updated on ${issue.updated_at}`
); );
await this.closeIssue(issue, closeMessage, closeLabel); await this.closeIssue(issue, closeMessage, closeLabel);
if (this.options.deleteBranch && issue.pull_request) { if (this.options.deleteBranch && issue.pull_request) {
core.info( issueLogger.info(
`Deleting branch for #${issue.number} as delete-branch option was specified` `Deleting branch for #${issue.number} as delete-branch option was specified`
); );
await this.deleteBranch(issue); await this.deleteBranch(issue);
this.deletedBranchIssues.push(issue); this.deletedBranchIssues.push(issue);
} }
} else { } else {
core.info( issueLogger.info(
`Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})` `Stale ${issueType} is not old enough to close yet (hasComments? ${issueHasComments}, hasUpdate? ${issueHasUpdate})`
); );
} }
@ -333,7 +345,9 @@ export class IssueProcessor {
sinceDate: string, sinceDate: string,
actor: string actor: string
): Promise<boolean> { ): Promise<boolean> {
core.info( const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(
`Checking for comments on issue #${issue.number} since ${sinceDate}` `Checking for comments on issue #${issue.number} since ${sinceDate}`
); );
@ -348,7 +362,7 @@ export class IssueProcessor {
comment => comment.user.type === 'User' && comment.user.login !== actor comment => comment.user.type === 'User' && comment.user.login !== actor
); );
core.info( issueLogger.info(
`Comments not made by actor or another bot: ${filteredComments.length}` `Comments not made by actor or another bot: ${filteredComments.length}`
); );
@ -371,7 +385,7 @@ export class IssueProcessor {
}); });
return comments.data; return comments.data;
} catch (error) { } catch (error) {
core.error(`List issue comments error: ${error.message}`); logger.error(`List issue comments error: ${error.message}`);
return Promise.resolve([]); return Promise.resolve([]);
} }
} }
@ -408,7 +422,7 @@ export class IssueProcessor {
); );
return issueResult.data; return issueResult.data;
} catch (error) { } catch (error) {
core.error(`Get issues for repo error: ${error.message}`); logger.error(`Get issues for repo error: ${error.message}`);
return Promise.resolve([]); return Promise.resolve([]);
} }
} }
@ -420,7 +434,9 @@ export class IssueProcessor {
staleLabel: string, staleLabel: string,
skipMessage: boolean skipMessage: boolean
): Promise<void> { ): Promise<void> {
core.info(`Marking issue #${issue.number} as stale`); const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Marking issue #${issue.number} as stale`);
this.staleIssues.push(issue); this.staleIssues.push(issue);
@ -444,7 +460,7 @@ export class IssueProcessor {
body: staleMessage body: staleMessage
}); });
} catch (error) { } catch (error) {
core.error(`Error creating a comment: ${error.message}`); issueLogger.error(`Error creating a comment: ${error.message}`);
} }
} }
@ -456,7 +472,7 @@ export class IssueProcessor {
labels: [staleLabel] labels: [staleLabel]
}); });
} catch (error) { } catch (error) {
core.error(`Error adding a label: ${error.message}`); issueLogger.error(`Error adding a label: ${error.message}`);
} }
} }
@ -466,7 +482,9 @@ export class IssueProcessor {
closeMessage?: string, closeMessage?: string,
closeLabel?: string closeLabel?: string
): Promise<void> { ): Promise<void> {
core.info(`Closing issue #${issue.number} for being stale`); const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Closing issue #${issue.number} for being stale`);
this.closedIssues.push(issue); this.closedIssues.push(issue);
@ -485,7 +503,7 @@ export class IssueProcessor {
body: closeMessage body: closeMessage
}); });
} catch (error) { } catch (error) {
core.error(`Error creating a comment: ${error.message}`); issueLogger.error(`Error creating a comment: ${error.message}`);
} }
} }
@ -498,7 +516,7 @@ export class IssueProcessor {
labels: [closeLabel] labels: [closeLabel]
}); });
} catch (error) { } catch (error) {
core.error(`Error adding a label: ${error.message}`); issueLogger.error(`Error adding a label: ${error.message}`);
} }
} }
@ -510,31 +528,35 @@ export class IssueProcessor {
state: 'closed' state: 'closed'
}); });
} catch (error) { } catch (error) {
core.error(`Error updating an issue: ${error.message}`); issueLogger.error(`Error updating an issue: ${error.message}`);
} }
} }
private async getPullRequest( private async getPullRequest(issue: Issue): Promise<PullRequest | undefined> {
pullNumber: number const issueLogger: IssueLogger = new IssueLogger(issue);
): Promise<PullRequest | undefined> {
this.operationsLeft -= 1; this.operationsLeft -= 1;
try { try {
const pullRequest = await this.client.pulls.get({ const pullRequest = await this.client.pulls.get({
owner: context.repo.owner, owner: context.repo.owner,
repo: context.repo.repo, repo: context.repo.repo,
pull_number: pullNumber pull_number: issue.number
}); });
return pullRequest.data; return pullRequest.data;
} catch (error) { } catch (error) {
core.error(`Error getting pull request ${pullNumber}: ${error.message}`); issueLogger.error(
`Error getting pull request ${issue.number}: ${error.message}`
);
} }
} }
// Delete the branch on closed pull request // Delete the branch on closed pull request
private async deleteBranch(issue: Issue): Promise<void> { private async deleteBranch(issue: Issue): Promise<void> {
core.info( const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(
`Delete branch from closed issue #${issue.number} - ${issue.title}` `Delete branch from closed issue #${issue.number} - ${issue.title}`
); );
@ -542,17 +564,19 @@ export class IssueProcessor {
return; return;
} }
const pullRequest = await this.getPullRequest(issue.number); const pullRequest = await this.getPullRequest(issue);
if (!pullRequest) { if (!pullRequest) {
core.info( issueLogger.info(
`Not deleting branch as pull request not found for issue ${issue.number}` `Not deleting branch as pull request not found for issue ${issue.number}`
); );
return; return;
} }
const branch = pullRequest.head.ref; const branch = pullRequest.head.ref;
core.info(`Deleting branch ${branch} from closed issue #${issue.number}`); issueLogger.info(
`Deleting branch ${branch} from closed issue #${issue.number}`
);
this.operationsLeft -= 1; this.operationsLeft -= 1;
@ -563,7 +587,7 @@ export class IssueProcessor {
ref: `heads/${branch}` ref: `heads/${branch}`
}); });
} catch (error) { } catch (error) {
core.error( issueLogger.error(
`Error deleting branch ${branch} from issue #${issue.number}: ${error.message}` `Error deleting branch ${branch} from issue #${issue.number}: ${error.message}`
); );
} }
@ -571,7 +595,9 @@ export class IssueProcessor {
// Remove a label from an issue // Remove a label from an issue
private async removeLabel(issue: Issue, label: string): Promise<void> { private async removeLabel(issue: Issue, label: string): Promise<void> {
core.info(`Removing label "${label}" from issue #${issue.number}`); const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Removing label "${label}" from issue #${issue.number}`);
this.removedLabelIssues.push(issue); this.removedLabelIssues.push(issue);
@ -590,7 +616,7 @@ export class IssueProcessor {
name: label name: label
}); });
} catch (error) { } catch (error) {
core.error(`Error removing a label: ${error.message}`); issueLogger.error(`Error removing a label: ${error.message}`);
} }
} }
@ -600,7 +626,9 @@ export class IssueProcessor {
issue: Issue, issue: Issue,
label: string label: string
): Promise<string | undefined> { ): Promise<string | undefined> {
core.info(`Checking for label on issue #${issue.number}`); const issueLogger: IssueLogger = new IssueLogger(issue);
issueLogger.info(`Checking for label on issue #${issue.number}`);
this.operationsLeft -= 1; this.operationsLeft -= 1;

View File

@ -0,0 +1,78 @@
import {Issue} from '../IssueProcessor';
import {IssueLogger} from './issue-logger';
import * as core from '@actions/core';
describe('IssueLogger', (): void => {
let issue: Issue;
let issueLogger: IssueLogger;
beforeEach((): void => {
issue = {
number: 8
} as Issue;
issueLogger = new IssueLogger(issue);
});
describe('warning()', (): void => {
let message: string;
let coreWarningSpy: jest.SpyInstance;
beforeEach((): void => {
message = 'dummy-message';
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
});
it('should log a warning with the given message and with the issue number as prefix', (): void => {
expect.assertions(2);
issueLogger.warning(message);
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
expect(coreWarningSpy).toHaveBeenCalledWith('[#8] dummy-message');
});
});
describe('info()', (): void => {
let message: string;
let coreInfoSpy: jest.SpyInstance;
beforeEach((): void => {
message = 'dummy-message';
coreInfoSpy = jest.spyOn(core, 'info').mockImplementation();
});
it('should log an information with the given message and with the issue number as prefix', (): void => {
expect.assertions(2);
issueLogger.info(message);
expect(coreInfoSpy).toHaveBeenCalledTimes(1);
expect(coreInfoSpy).toHaveBeenCalledWith('[#8] dummy-message');
});
});
describe('error()', (): void => {
let message: string;
let coreErrorSpy: jest.SpyInstance;
beforeEach((): void => {
message = 'dummy-message';
coreErrorSpy = jest.spyOn(core, 'error').mockImplementation();
});
it('should log an error with the given message and with the issue number as prefix', (): void => {
expect.assertions(2);
issueLogger.error(message);
expect(coreErrorSpy).toHaveBeenCalledTimes(1);
expect(coreErrorSpy).toHaveBeenCalledWith('[#8] dummy-message');
});
});
});

View File

@ -0,0 +1,31 @@
import * as core from '@actions/core';
import {Issue} from '../IssueProcessor';
import {Logger} from './logger';
export class IssueLogger implements Logger {
private readonly _issue: Issue;
constructor(issue: Readonly<Issue>) {
this._issue = issue;
}
warning(message: Readonly<string>): void {
core.warning(this._prefixWithIssueNumber(message));
}
info(message: Readonly<string>): void {
core.info(this._prefixWithIssueNumber(message));
}
error(message: Readonly<string>): void {
core.error(this._prefixWithIssueNumber(message));
}
private _prefixWithIssueNumber(message: Readonly<string>): string {
return `[#${this._getIssueNumber()}] ${message}`;
}
private _getIssueNumber(): number {
return this._issue.number;
}
}

View File

@ -0,0 +1,73 @@
import {Logger} from './logger';
import * as core from '@actions/core';
describe('Logger', (): void => {
let logger: Logger;
beforeEach((): void => {
logger = new Logger();
});
describe('warning()', (): void => {
let message: string;
let coreWarningSpy: jest.SpyInstance;
beforeEach((): void => {
message = 'dummy-message';
coreWarningSpy = jest.spyOn(core, 'warning').mockImplementation();
});
it('should log a warning with the given message', (): void => {
expect.assertions(2);
logger.warning(message);
expect(coreWarningSpy).toHaveBeenCalledTimes(1);
expect(coreWarningSpy).toHaveBeenCalledWith('dummy-message');
});
});
describe('info()', (): void => {
let message: string;
let coreInfoSpy: jest.SpyInstance;
beforeEach((): void => {
message = 'dummy-message';
coreInfoSpy = jest.spyOn(core, 'info').mockImplementation();
});
it('should log an information with the given message', (): void => {
expect.assertions(2);
logger.info(message);
expect(coreInfoSpy).toHaveBeenCalledTimes(1);
expect(coreInfoSpy).toHaveBeenCalledWith('dummy-message');
});
});
describe('error()', (): void => {
let message: string;
let coreErrorSpy: jest.SpyInstance;
beforeEach((): void => {
message = 'dummy-message';
coreErrorSpy = jest.spyOn(core, 'error').mockImplementation();
});
it('should log an error with the given message', (): void => {
expect.assertions(2);
logger.error(message);
expect(coreErrorSpy).toHaveBeenCalledTimes(1);
expect(coreErrorSpy).toHaveBeenCalledWith('dummy-message');
});
});
});

15
src/classes/logger.ts Normal file
View File

@ -0,0 +1,15 @@
import * as core from '@actions/core';
export class Logger {
warning(message: Readonly<string>): void {
core.warning(message);
}
info(message: Readonly<string>): void {
core.info(message);
}
error(message: Readonly<string>): void {
core.error(message);
}
}