Who Ran That Report: The Log Behind Every Number That Left the Building
A figure nobody should have appears in a WhatsApp group, and the question is who took it out. Running a report and exporting it are two separate recorded acts here — which is the distinction that makes the question answerable at all.
Someone forwards a screenshot into a group chat. It is a list of your customers with their outstanding balances, and it is real — the totals are right, the formatting is yours. Two hours of increasingly uncomfortable conversation follow, and it ends where these things usually end: with a general appeal for professionalism, because nobody can establish who produced the file. Everybody had access. Nobody has a record.
That outcome is not inevitable. It is the consequence of a reporting layer that treats looking at data and taking data away as the same event, and therefore logs neither properly. This one does not, and the design decision behind that is worth understanding before you need it.
Three records, not one
When somebody produces a report here, three separate things are written down. They are easy to confuse and they answer completely different questions.
What exists after a report has been run and sent
The definition — what the report *is*
A saved report: its dataset, its chosen columns, its owner, its visibility, its charts. This changes rarely and is the thing people mean when they say "the debtors report". On its own it tells you nothing about who looked at anything.
The run — what somebody *asked*
Every execution records who ran it, the filters they applied, how many rows came back, how long it took, and whether it succeeded or failed with an error. The filters are the part people overlook, and they are the most revealing field in the whole log.
The export — what somebody *took*
A separate record attached to the run: the format, the file, the size in bytes, and its own actor. Because the person who exports a run is not necessarily the person who ran it, and a file leaving the system is a different act from a screen being read.
The lifecycle event — what somebody *changed*
Creating, editing, sharing or pinning a report writes an audit entry carrying the actor with their name and email, the action, the route, the IP address, the user agent and the session. Report administration is audited like anything else that matters.
Row-level reads
Nothing records which individual rows a person looked at within a run, only that the run returned a certain number of them. If you need per-record access history on a specific customer, this is not where it lives.
The separation of run from export is the load-bearing one. A great many systems log "report viewed" and stop, which means the highest-risk event — a spreadsheet of personal data written to a file and carried off — is the one event with no record. Splitting them means you can ask the narrow question, and the narrow question is usually the one you actually have.
Reconstructing an incident, field by field
Take the screenshot in the group chat. Here is what the log makes it possible to establish, and where it stops.
From a leaked screenshot to a name
Note the last row honestly: the log proves an export, not a leak. That is still transformative. The difference between "somebody in this company did this" and "this file was exported by this person at this time" is the difference between a meeting nobody remembers and a matter that gets resolved. It also clears everyone else, which matters more to the innocent than to the guilty.
Why the filters field is the one to pay attention to
The run record stores the filters that were applied, not just the report that was run. This sounds like housekeeping and is actually the difference between a log that identifies people and one that does not.
One saved report is not one body of data. The same debtors definition run with a single-branch filter and a two-week window returns a handful of rows that a branch supervisor is entitled to see. Run unfiltered across every branch and every period, it returns the whole book. Same report, same permission, radically different act — and only the filters distinguish them. A log that records "ran the debtors report" cannot tell those two apart. One that records the filters can, and the row count corroborates it.
Two fields that stop data leaving in the first place
Detection after the fact is the weaker half of the answer. Per-field settings let you mark a column as sensitive, and separately as export-restricted, which is a distinction worth dwelling on: a column can be readable on screen by someone who needs it and still be barred from the file that walks out of the building. National ID numbers, bank details, individual salaries and personal phone numbers are the obvious candidates.
Combine that with who can see which reports and you have prevention plus a record, which is the pairing that actually holds. Restriction alone gets worked around; logging alone finds out afterwards.
Reporting on the log itself
Both the audit log and the system log are available as reporting datasets in their own right, which means the trail is queryable with the same report builder as everything else rather than being a screen you scroll through hoping to spot something. Filter by actor, by action, by date, by module; export it; put it on a schedule so a monthly summary of report and export activity arrives without anybody remembering to look.
That last suggestion is the single highest-value thing in this article. An audit trail nobody reads is a forensic tool, useful once you already have a problem. The same trail delivered monthly is a deterrent, and the deterrent is worth more than the forensics.
What AWRA OpsHub does today
- Every report run recorded with actor, applied filters, row count, duration and success or failure including the error text.
- Exports recorded separately from runs, each with its own actor, format, file path and size in bytes.
- Report lifecycle actions — create, edit, share, pin — audited with actor name and email, action, route, IP address, user agent and session.
- Per-field sensitivity and a separate per-field export restriction, so a column can be visible on screen and barred from files.
- Audit and system logs exposed as reporting datasets, so the trail is filterable, exportable and schedulable like any other report.
- Durations retained per run, which doubles as the way you find the report quietly straining the database.
What it does not do
- No row-level read tracking. You know a run returned 400 rows; you do not know which 400, or that a particular customer's record was among them.
- No alerting on unusual export behaviour. Nothing notices that somebody exported eleven reports at 23:40 on their last week of employment. You would have to look.
- No watermarking or per-recipient tagging of exported files, so a leaked file cannot be traced back to a copy by inspecting the file itself.
- No blocking of exports outside working hours or from unexpected locations.
- No automatic retention or purge policy on run history — it accumulates, which is convenient for investigations and worth a decision of your own.
- The trail proves an export, never a leak. It narrows the question to a person; it does not answer it.
The absence of export alerting is the gap we would close first, and it is worth naming plainly because the scheduled-report workaround is genuinely decent: a monthly export-activity summary going to somebody senior converts an unwatched log into a watched one, using only features that exist. Do that rather than waiting.
A routine that costs about twenty minutes a quarter
Making the trail worth having
- Mark the export-restricted columns before anyone asks. ID numbers, bank details, individual pay, personal contacts. Screen-readable and file-barred is the correct setting for most of them, and setting it takes minutes.
- Schedule a monthly report-and-export activity summary to somebody senior. This single step is what turns a forensic record into a deterrent. Staff who know exports are reviewed behave differently, and they should.
- Review the actor list quarterly against current employees. A run by somebody who left in March is a much more interesting finding than any single export, and it points at an offboarding failure rather than a reporting one.
- Check the failure and duration columns while you are there. Reports erroring repeatedly usually mean a dataset changed under a saved definition; reports taking a very long time are the ones affecting everybody else's experience.
- Write down what you would do with an unexplained export, before you have one. Who investigates, who is told, what the threshold for escalation is. Decided in advance it is a procedure; decided during it is an argument.
- Do not purge run history casually. It is the only account of who saw what. If you decide on a retention period, decide it deliberately rather than discovering that somebody tidied it up.
The obligation is not hypothetical
Kenya's data protection framework expects an organisation holding personal data to be able to say who accessed it and why, and to demonstrate that access was controlled rather than merely assert it. A customer list with names, phone numbers and balances is personal data by any reading. The practical point is that this capability is not something you can retrofit after a complaint — the log either recorded the run at the time or it did not. It is recording. What is optional is whether anybody ever looks.
Our take
The valuable design decision here is that running a report and exporting one are separate records with separate actors, because the export is the event that matters and it is the one most systems fail to distinguish. Set your export-restricted fields now rather than after an incident, and schedule a monthly activity summary to somebody senior — that converts a log nobody reads into a control that changes behaviour, using only what already exists. Accept the two real limits: there is no row-level read history and nothing alerts you to unusual export volume, so anomalies are found by looking rather than by being told. And remember what the trail actually proves. It identifies who exported a file, which narrows a general suspicion to a specific conversation, and clears everyone else at the same time.
Every run, every export, with a name on it
Report runs logged with actor, filters, row count and duration; exports recorded separately with their own actor and file size; per-field export restrictions; and the audit trail itself available as a reportable, schedulable dataset.
See plans & pricingFrequently asked questions
Can we find out who exported a particular report?
Yes. Exports are recorded separately from runs, each with its own actor, format, file path and size in bytes, attached to the run that produced them. Because the run also stores the filters applied and the number of rows returned, you can usually narrow a leaked file to one specific execution and one named person, then see who exported it.
Why does it matter that runs and exports are logged separately?
Because reading a report on screen and writing a file that can be forwarded are different acts with different risk. Many systems log only "report viewed", which means the highest-risk event has no record at all. Separating them lets you ask the narrow question — who took a copy — which is nearly always the question you actually have.
Does the log show which specific records someone looked at?
No. There is no row-level read tracking. The run record tells you a query returned a certain number of rows under certain filters; it does not tell you which rows, so you cannot use it to show whether one particular customer's record was included. If you need per-record access history on a named individual, this is not the mechanism.
Can we stop sensitive columns being exported at all?
Yes, and this is more useful than most people expect. Fields can be marked sensitive and, separately, export-restricted — so a column stays readable on screen for people who need it while being barred from files that leave the building. National ID numbers, bank details, individual salaries and personal phone numbers are the usual candidates.
Will we be alerted if someone exports an unusual amount of data?
No. Nothing watches export volume or timing, so eleven exports at midnight during somebody's notice period would pass unremarked. The practical workaround uses only existing features: schedule a monthly report-and-export activity summary to somebody senior. An audit trail that is read monthly deters; one that is only read after an incident merely documents.
Can we build reports on the audit trail itself?
Yes — the audit log and the system log are both available as reporting datasets, so the trail can be filtered by actor, action, module and date, exported, and put on a schedule like any other report. That is what makes routine review practical rather than a matter of scrolling through a screen hoping something stands out.