Audit · Guides
Record events
Each event says who did what, to what and about whom. Audit::record() checks it and writes it to the outbox, in the same transaction as the action.
Record what you would have to answer for: data about a person shown, changed, exported or deleted, a secret revealed, a permission given. Record reads as well as changes: a person's access report then shows who saw their data, not only who changed it.
use Illuminate\Support\Facades\DB;
use MyBeast\Audit\Laravel\Events\Actor;
use MyBeast\Audit\Laravel\Events\Target;
use MyBeast\Audit\Laravel\Facades\Audit;
DB::transaction(function () use ($customer, $operator, $reason) {
// … show or change the customer …
Audit::record('customer.viewed',
Actor::operator($operator->id),
Target::of('customer', $customer->id),
['fields' => ['iban', 'address']],
subject: $customer->user_id,
account: $customer->workspace_id,
reason: $reason,
);
});Audit::record() returns the event's id, a UUID. The parts after the action are optional except the actor.
In the action's transaction
The event is written to the outbox table on your database connection. Inside DB::transaction(), the outbox row commits with the action or rolls back with it: no action without its record, and no record of an action that didn't happen. Outside a transaction, the row is written at once.
The row joins the transaction only on the same connection. If the actions you record run on another connection than the default one, set MYBEAST_AUDIT_CONNECTION to its name before the first migrate, so the outbox lives there too.
Name the action
The action says what happened, in your words: lower-case letters and digits joined by ., _ or -, starting with a letter, at most 80 characters. For example customer.viewed, key.revealed, lease.exported.
Name the thing first and what was done to it second. The search takes the start of an action with a *, so customer.* finds everything done to customers.
Who did it
Every event has an actor, by your id for them:
| Actor | Who |
|---|---|
Actor::person($id) | One of your users: a customer, a tenant, a member of an account. |
Actor::operator($id) | One of your staff: support, an admin of your console. |
Actor::system('scheduler') | Your app itself: a job, a command, an import. The id names which. |
An id is a string or an integer: 1 to 64 letters, digits, ., _, : or -. The same rule holds for every id below.
What it was done to
Target::of('customer', $customer->id) names its kind and your id for it. The kind is written like an action, at most 40 characters. Target::person($id) is a person as the target: the event is then in that person's access report too. An event can have no target, such as a sign-in.
Whom it is about
subject:is the person the data is about, by your id. Their access report lists every event that names them here or as a person target.account:is the account it happened in: a workspace, an organisation, a customer. The search finds every event of one account.
The reason
reason: is the operator's reason, when your console asked for one: "Support ticket 7781: the tenant asked why their rent went up." It is kept as typed, up to 500 characters; longer is cut. An email address typed into it is replaced with [e-mail removed], because a reason is a person's words, not your code's.
The context
The fourth argument is anything else worth keeping, as a keyed array: the fields shown, where it was done from, the old and new value of a setting. At most 4 levels deep and 8 KB as JSON. Keep ids and short facts, never documents or the personal data itself: ['fields' => ['iban', 'address']], not the IBAN.
Ids, never names or email addresses
Audit keeps your ids for people and accounts, never their names or email addresses. An email address as an id, or anywhere in the context, throws InvalidAuditEvent. Audit itself refuses an event with an email address anywhere in it, so one sent past the SDK is refused too.
Who an id is, your own console says. In the project's Settings at services.my-beast.com, Links takes an address for a person and one for an account, each with the id's placeholder: Audit's pages then link each id to its page in your console.
The request it came from
Each event carries the id of the request it was recorded in, so its page in Audit opens that request's log lines in Logs and its errors in Errors. The SDK takes it from:
mybeast/logs-laravel, when your app runs it. A queued job carries the id of the request that queued it.- Otherwise the request's
X-Request-Idheader, from your proxy or load balancer. SetMYBEAST_AUDIT_REQUEST_ID_HEADERto read another header, or to nothing to read none.
request: names one yourself. An id that isn't 1 to 64 letters, digits, ., _, : or - is left off, and the event still goes. Neither SDK needs the other.
The time
An event happened when it was recorded, by your server's clock, kept to the millisecond in UTC. at: takes another time, such as when an import ran. Audit refuses an event more than 5 minutes ahead of its own clock, so keep your servers' clocks set (NTP).
Events are numbered in the order Audit takes them in, which is the order your outbox sends them. The time is what they say; the number is their place in the chain.
When an event is wrong
A wrong event throws InvalidAuditEvent at once, inside your transaction, so the mistake is seen where it is made, not found in the outbox weeks later. Audit::fake() checks events the same way, so your tests catch it.
| When | The message starts |
|---|---|
The action is not lower-case letters and digits joined by ., _ or -, or is longer than 80 | The action "…" must be lower-case letters and digits joined by . _ or - … |
| A target's type is not written like an action, or is longer than 40 | The target type "…" must be lower-case letters and digits … |
| An actor, a target, the subject or the account is an email address | The actor is an e-mail address: give your id for them … |
An id is empty, longer than 64, or has other characters than letters, digits, ., _, : or - | The subject id "…" must be 1-64 letters, digits, . _ : or -. |
| The context is a list, not keyed | The context must be keyed (an object), not a list. |
| The context nests deeper than 4 levels | The context nests too deep (at most 4 levels). |
| The context is over 8 KB as JSON | The context is too big (at most 8 KB as JSON) … |
| The context holds an email address | The context holds an e-mail address: ids only, never names or e-mails. |
A thrown event rolls the action back
DB::transaction() the exception rolls the action back with its record, unless you catch it. That is on purpose: an action that can't be recorded doesn't happen.