What the public log shows
Qoffee is designed to run in a public repository, because that is what makes the Actions minutes free. The consequence is that the run logs are readable by anyone.
So identifiers are replaced before they reach the log. This page shows the code that does it rather than describing it, because you can check code.
What a line looks like
14:22:07 3f9a1c2b INFO qoffee: found 3 tracked job(s)
14:22:09 3f9a1c2b INFO qoffee.core.engine: plan: notify=True actions=3
14:22:09 3f9a1c2b INFO qoffee.core.engine: resolve job#3beed3 - completed successfully
job#3beed3 is a truncated SHA-256 of the real job ID.
It is stable, so the same job produces the same hash everywhere in the log and you can follow it across lines.
It is one way, so it does not give the ID back.
The full ID is in your notification, which only you can see.
What gets replaced
- Your IBM token and instance CRN, registered as literal strings the moment configuration loads.
- Every job ID, registered as a literal string as soon as the jobs are fetched.
- Anything matching
crn:v1:..., wherever it appears, including inside URLs. - Anything that looks like an IBM job ID, as a backstop for identifiers that were never registered.
Where the filtering happens
Not at the call sites. At the handler, once.
class RedactionFilter(logging.Filter):
def filter(self, record):
if not self.enabled:
return True
record.msg = redact(record.getMessage())
record.args = ()
if record.exc_info:
formatter = logging.Formatter()
record.exc_text = redact(formatter.formatException(record.exc_info))
record.exc_info = None
return True
Redacting at each call site leaks the first time anyone adds a debug line and forgets. Filtering at the handler catches everything that reaches the log whether or not the person who wrote it was thinking about redaction.
The exception traceback handling is the part that matters most in practice. Tracebacks from the IBM SDK can carry your instance CRN inside a request URL, and a traceback is formatted by the handler after every filter has run, so it has to be rendered early and redacted explicitly.
Ordering
Logging is configured with redaction on before configuration is even loaded. A configuration error that echoes a credential therefore cannot reach the log unfiltered, because the filter was installed first.
Turning it off
REDACT_LOGS = False in qoffee/settings.py.
There is no reason to do this in a public fork. The full IDs are already in your notification, so redaction costs you nothing there. In a private fork where you are debugging, it can be convenient.
What this does not cover
Redaction protects the log. It is not a guarantee about anything else.
Your secrets are held by GitHub and are unreadable once saved, including by you. Your notification goes to your own webhook. Qoffee makes no other outbound calls, and there is no server anywhere in the design, which is covered on how Qoffee works.
All of the above is in qoffee/logging_setup.py, which is 107 lines.