Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Say Logger Laravel Package

jordanpartridge/laravel-say-logger

A Laravel logging channel that speaks your log messages out loud using the macOS say command, so errors and warnings reach you even when you're away from the terminal. Handy for local development and debugging.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require jordanpartridge/laravel-say-logger

Wire it into a logging stack — the package's channel is added alongside a normal file channel so you keep durable logs:

// config/logging.php
'stack' => [
    'driver'   => 'stack',
    'channels' => ['single', 'say'],
],

Then confirm the plumbing works before trusting it:

which say            # must exist -> macOS only
say -v '?' | head    # list installed voices
php artisan tinker
>>> Log::info('say logger smoke test');

Where to look first

  1. vendor/jordanpartridge/laravel-say-logger/src/ — it's a small package; read the handler/service provider before you rely on it. You specifically want to see how it invokes say (exec, shell_exec, proc_open, Symfony Process) and whether it backgrounds the process.
  2. The published config (try php artisan vendor:publish --tag=say-logger-config, otherwise check the README) — that's the voice-per-level map.
  3. config/logging.php — the channel definition is where you'll do 90% of your tuning.

First real use case: not "make all logs speak." Add the say channel to a separate channel or an error-only stack so a failed queue job, a 500, or a long-running command finishing announces itself while you're in another window. That's the whole value proposition: an out-of-band alert for the one thing you care about, not narration for every Log::debug.

Implementation Patterns

Keep a file channel, always. ['single', 'say'] is right; ['say'] alone means your logs only exist as audio. Never do that.

Filter by level at the channel, not in your code. Speech is a scarce resource — one utterance per second, not per log line. Configure the say channel with 'level' => 'error' (or critical) so debug/info noise never reaches the speaker. Reserve the fun voices for things you genuinely want sung at you.

Use a dedicated channel for deliberate announcements rather than routing everything:

// A long import or backfill — tell me when it's done
Log::channel('say')->info('Import finished, 12,430 rows');

// In withExceptions() — only emergencies bypass the error log
->report(function (Throwable $e) {
    if ($e instanceof QueryException) {
        Log::channel('say')->critical(class_basename($e));
    }
})

Environment-gate it. The channel should be inert outside local. Two layers: don't include say in the stack in production config, and set any package-level enable flag from .env (SAY_LOGGER_ENABLED=false in .env.example, phpunit.xml, and CI).

Long-running processes are the sweet spot. Horizon/queue workers, artisan serve, scheduler tasks, and deploy scripts log into the void; a spoken "job failed" is a genuinely better signal than a line you'll never tail. One announcement per job failure, not per retry attempt.

Rate-limit yourself. If a loop logs 50 errors, you'll get 50 overlapping utterances. Wrap the say channel behind a small helper that dedupes identical messages within N seconds, or use the say channel only from code paths you know are single-shot.

Testing. Log::spy() / Log::shouldReceive() assertions cover your own calls; assert on the say channel name specifically. And make sure the test suite never actually speaks — that's both slow and maddening.

Extension point worth building: if your team is mixed-OS, wrap the TTS invocation yourself. A small TextToSpeech contract with a SayDriver (macOS), SpdSayDriver (Linux), and NullDriver (CI) is ~40 lines and makes the package usable rather than a local-only curiosity.

Gotchas and Tips

  • macOS only. The say binary ships with macOS and nothing else. On Linux, Windows, Alpine/Docker, and most CI runners the channel will either throw or silently no-op — verify which, because a throwing log handler can take down a request. Guard with PHP_OS_FAMILY === 'Darwin'.
  • Verify the "async" claim. say blocks until the audio finishes unless the package backgrounds it (... &) or uses a non-blocking process. If it's a plain exec(), a five-second utterance becomes five seconds of extra response time on a web request. Test with time php artisan tinker around a single Log::error. If it blocks, only ever use it from CLI contexts.
  • Shell interpolation is an injection surface. Log messages routinely contain user input: quotes, backticks, $(...), semicolons. If the handler builds a command string, a message like He said "hi"; rm -rf ~ can break it or worse. Read the source; if it doesn't use escapeshellarg or array-based proc_open, wrap/override it before pointing it at anything user-influenced.
  • Voice names are not portable. "Bubbles", "Cellos", "Organ", "Bad News" are optional downloads, and the set changes between macOS versions. A missing voice makes say error or fall back to the default. say -v '?' is your diagnostic, and users may need System Settings → Accessibility → Spoken Content → Manage Voices.
  • Silence is the most likely failure mode, and the hardest to diagnose. Muted output, wrong audio device, headless Mac, or SSH session with no audio routing → the code works, you hear nothing. Always confirm with say "hello" in a terminal first, then test the package.
  • Audio queue backlog. Utterances queue rather than drop. After a burst of errors you may be listening to a stale backlog for a minute. Prefer short messages — the exception class name, not the full message with a stack trace.
  • Privacy and open offices. Log messages contain emails, tokens, tenant names, and occasionally credentials. Spoken aloud, in a shared space, on a speaker. Restrict to error-level, keep messages terse, and never enable this on a machine that isn't yours.
  • Noisy neighbours. If you tail your log file into anything else, remember two channels now exist; check storage/logs is still being written before you trust the audio as confirmation that logging works.
  • Treat the code as unreviewed. It's a v1.0.0 novelty package and the release notes proudly state it was built with Claude Code. That's fine — but read the handler, check what it shells out to, and pin the version in composer.json rather than floating ^1.0.
  • Novelty is the point, but scope it. This is a developer-ergonomics toy for local machines. Don't add it to a team-wide composer.json require block; put it in require-dev so production images don't carry a channel that can only fail on Linux.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource