Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All documentation

Documentation

Wake

Connecting your apps

Updated

Scalpels connects an application to Wake the way it connects one to Hone or Sink. Pick the Cloud environment or Forge site on your dashboard and choose Connect beside it, and Scalpels mints an ingest credential on your Wake deployment, writes WAKE_URL and WAKE_TOKEN onto that application's environment, and opens a draft pull request adding artisan-build/wake-client to its composer.json. Wake creates the application's record as the credential is minted, so there is nothing to add in its admin pages first. Attach to an existing app is the Scalpels side of it, including what the pull request touches and why Scalpels cannot merge it for you.

Two things are left to do afterwards, and the second one is the one people miss.

Merge the install pull request

The pull request is a draft, and it does not carry composer.lock, so check the branch out, run the composer update line the pull request shows, commit the lock file and merge. Until it deploys, the application has the credential and not the client.

Add wake to the log stack

The client registers a wake log channel and Laravel writes to it only once the application's log stack names it. Scalpels does not set that for you, because the right value is whatever channels the application already had plus wake, and no fixed value is right for every application: writing single,wake over LOG_STACK=stderr would take the application off Laravel Cloud's own log stream, and an application whose LOG_CHANNEL is not stack never reads LOG_STACK at all.

So on the reporting application's environment:

LOG_STACK=single,wake

keeping whatever was already there and adding wake to the end.

Connecting by hand

For an application Scalpels does not manage, the same three values set by hand do the same job:

WAKE_URL=https://wake.example.com
WAKE_TOKEN=the-token-Wake-showed-you-once
LOG_STACK=single,wake

Create the record in Wake's Apps pages first and copy the credential it shows once, then composer require artisan-build/wake-client in the reporting application.

WAKE_URL is the base URL of your Wake deployment. Do not append a path: the client builds /api/v1/ingest itself, and a URL that already carries the path addresses something that does not exist. This is the one value Scalpels gets right for free, and the commonest thing to get wrong by hand.

Check the wiring

php artisan wake:doctor

That reports the configuration it resolved, whether it can reach your deployment, and the result of one real round trip. Run it in the environment that will do the reporting rather than locally, since the credential and the URL are per environment.

The client will not hurt the application

The handler buffers events in memory and flushes when the request, console command or queue job ends, and when the buffer fills. Connect and read timeouts are short. A delivery failure is swallowed and counted rather than raised, so a Wake outage cannot fail your requests, and the drop total rides along on the next batch Wake accepts, so the loss shows up in Wake's ingest health instead of disappearing. There is a recursion guard, so Wake's own failures are never logged into Wake.

That design has a consequence worth stating: a misconfigured client is quiet. Nothing in the reporting application complains. wake:doctor and the application's ingest health in Wake are how you find out.

Redact before it leaves

Wake sends and stores payloads unchanged. An exception message or a context array can hold a token, a password reset link, or somebody's personal data, and all of it will be in the lake and readable by anyone with an MCP credential.

The client calls a payload filter on every event. The default passes everything through. Implement the contract and bind it in the reporting application's own service provider:

namespace App\Logging;

use ArtisanBuild\WakeClient\Contracts\PayloadFilter;
use ArtisanBuild\WakeContracts\LogEvent;

final class RedactWakePayload implements PayloadFilter
{
    public function filter(LogEvent $event): ?LogEvent
    {
        if ($event->channel === 'private-audit') {
            return null;
        }

        return new LogEvent(
            eventId: $event->eventId,
            occurredAt: $event->occurredAt,
            level: $event->level,
            channel: $event->channel,
            message: $event->message,
            context: $this->redact($event->context),
            extra: $this->redact($event->extra),
            exception: $event->exception,
            environment: $event->environment,
            hostname: $event->hostname,
            correlationId: $event->correlationId,
        );
    }
}

Returning null drops the event entirely, which is the right answer for a channel that should never leave the application. Returning a replacement LogEvent ships a changed payload. The filter runs in the reporting application, so a value you remove there never crosses the network.

What gets captured

Each event carries the message, level and channel, the context and extra arrays, the exception class, message, file, line and a truncated trace, the timestamp to the microsecond, the application environment, the hostname, and a correlation id tying the lines from one request or job together. Ingest rejects a body that names an application: identity comes from the credential, so a reporting application cannot claim to be a different one.