Skip to content

Instantly share code, notes, and snippets.

@tslmy
Last active September 4, 2023 21:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tslmy/84f34a25babe045eb302ec72f2bf39eb to your computer and use it in GitHub Desktop.
Save tslmy/84f34a25babe045eb302ec72f2bf39eb to your computer and use it in GitHub Desktop.
How to log messages from Python to GitHub as Checks within a Jenkinsfile
import logging
from datetime import datetime
from github import GitHubIntegration
class GitHubCheckHandler(logging.handler):
"""
A logging handler that sends messages to GitHub as a Check.
"""
def __init__(
self,
private_key_path: str = "my-github-app.2023-09-03.private-key.pem",
github_app_id: int = 123,
installation_id: int = 456,
owner_repo: str = "username/repo_name",
head_sha: str = "377e195414498fe4c97cb5a386fbaf33a145acbfb51ab7169a0fa6136314a079",
details_url: str = "https://jenkins.example.com/job/MyJob/42/console",
base_url: str = "https://api.github.com",
):
"""
- `base_url`: If using GitHub Enterprise, it's usually `https://github.your.company.com/api/v3`.
- `details_url`: Defines the link on the GitHub PR Checks tab that refers back to the Jenkins Build.
"""
super().__init__()
with open(private_key_path) as f:
private_key = f.read()
self.github_integration = GitHubIntegration(
integration_id=github_app_id,
base_url=base_url,
private_key=private_key,
)
self.head_sha = head_sha
self.installation_id = installation_id
self.owner_repo = owner_repo
self.check_run = None
self.details_url = details_url
def emit(self, record: logging.LogRecord):
"""
Implements the interface of the `handler` class.
This is called every time you log a message via a logger.
"""
output = {
"title": record.getMessage(),
"summary": record.getMessage(),
}
# If a Check Run has already been created, we don't want to create another, because that would require users on the Checks tab to refresh the page to see the new messages, instead of just having them show up in real time, async.
if self.check_run:
self.check_run.edit(output=output)
# Return early.
return
# If a Check Run hasn't been created, we create one.
gh = self.github_integration.get_github_for_installation(self.installation_id)
repo = gh.get_repo(self.owner_repo)
self.check_run = repo.create_check_run(
# Use the logger's name as the name of the PR Check.
name=record.name,
head_sha=self.head_sha,
details_url=self.details_url,
status="in_progress",
output=output,
)
def conclude(self, conclusion: str, output=None):
"""
Conclusion can be one of action_required, cancelled, failure, netural, success, skipped, stale, timed_out.
"""
if not self.check_run:
# Nothing to conclude.
return
if not output:
output = self.check_run.output
self.check_run.edit(
completed_at=datetime.now(),
conclusion=conclusion,
output=output,
status="completed",
)
def attach_logger_to_github(logger):
if (
os.getenv("GITHUB_APP_PRIVATE_KEY_PATH")
and os.getenv("GITHUB_APP_ID")
and os.getenv("GITHUB_APP_INSTALLATION_ID")
and os.getenv("GITHUB_PR_HEAD_SHA")
and os.getenv("GITHUB_REPO_SSH_URL")
and os.getenv("BUILD_URL")
):
handler = GitHubCheckHandler(
private_key_path=str(os.getenv("GITHUB_APP_PRIVATE_KEY_PATH")),
installation_id=int(str(os.getenv("GITHUB_APP_INSTALLATION_ID"))),
github_app_id=int(str(os.getenv("GITHUB_APP_ID"))),
owner_repo=str(os.getenv("GITHUB_REPO_SSH_URL"))
.removeprefix("git@github.com:")
.removesuffix(".git"),
commit_sha=str(os.getenv("GITHUB_PR_HEAD_SHA")),
details_url=str(os.getenv("BUILD_URL")),
)
logger.addHandler(handler)
return handler
if __name__ == "__main__":
logger = logging.getLogger("test-handler")
handler = attach_logger_to_github(logger)
logger.fatal("test-message")
handler.conclude("success")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment