Download the PHP package slogger/laravel without Composer

On this page you can find all versions of the php package slogger/laravel. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel

SLogger Laravel

English | Русский

SLogger Laravel is a tracing/observability package for Laravel apps. It records request/command/job/event/etc. traces and delivers them to a remote backend via configurable dispatchers.

This README documents installation, configuration, watchers, masking, dispatchers, and usage patterns.

Upgrading to 2.0

Masking moved out of the traced application and into the dispatcher job.

Requirements

Installation

1) Install the package (via Composer in your app):

2) Publish config:

3) Configure env and config/slogger.php (see below).

Quick Start

Enable and use the queue dispatcher:

Then start dispatcher workers:

Trace Collection Backend

SLogger Laravel sends traces to a separate backend service. The reference backend project is:

Use its setup instructions to provision the server and configure the API client URL/token in this package.

Custom backend / client

You can replace the backend by providing your own API client. Redefine ApiClientFactory::create and return a custom implementation of SLoggerLaravel\\Dispatcher\\ApiClients\\ApiClientInterface that sends traces to your backend.

Configuration

All configuration lives in config/slogger.php with environment overrides. Key sections:

General

Dispatchers

SLOGGER_DISPATCHER_QUEUE_CONNECTION is required for the queue dispatcher — there is no fallback to QUEUE_CONNECTION on purpose: telemetry must not silently share the application queue connection. Use a dedicated connection.

SLOGGER_DISPATCHER_QUEUE_SOCKET_CLIENT_TIMEOUT is the read/write timeout of the socket client in seconds (default 10). It gives headroom when the receiver is saturated and its acknowledgement is genuinely late; the connect timeout is separate and stays at 2 seconds.

If the receiver closes the connection (restart, deploy, network fault), the client detects it and reconnects transparently — exactly one retry per batch. Timeouts are not retried this way: that would turn a saturated receiver into a reconnect storm; they are handled by the job retry policy below.

Send retries are fixed by design: 5 attempts with backoff of 5/10/30/60 seconds between them. After the attempts are exhausted the batch is dropped with a rate-limited warning in the SLogger log channel — telemetry never fills the failed_jobs storage.

A failed attempt never reaches the application's exception handler: the job releases itself with the pause instead of throwing - the worker reports whatever escapes handle(), attempt by attempt, and a receiver restart used to become thousands of errors in the host's own monitoring. The exception is a sync connection, which has no later attempt, so the error is thrown there as before.

Request parent trace header

Allows linking child traces to parent requests via a custom header. The middleware reads it from the incoming request and sets it on the response it returns, so the client actually receives it. (It used to be set in terminate(), which under FPM runs after the response has already been sent - cross-service correlation only ever worked in tests.)

Watchers (enable/disable)

What SLogger Writes

Each trace contains:

memory is the percentage of memory_limit in use, and is null when there is no limit (memory_limit = -1, the CLI default) - there is nothing to be a percentage of. cpu is the one-minute load average as a percentage of the machine's capacity, normalised by core count, and can exceed 100 on an overloaded machine.

Watcher data highlights:

A queue worker fails a timed out job from its SIGALRM handler, i.e. in the middle of whatever the job was doing, and kills itself right after. Traces started by the job and still open at that moment are closed as failed and tagged __interrupted, keeping the data they had collected; a job that is retried instead of failed is closed by the JobTimedOut event. A trace can still be left in the started status when the signal arrives while a trace is being pushed to the dispatcher — tracing is paused there, so the timeout events are dropped — or when the worker is killed by a signal it does not handle.

Requests

Middleware

For HTTP request tracing, add the middleware to the routes you want traced:

Request watcher config

config/slogger.php:

only_paths

Patterns use Laravel Str::is matching.

JSON response size

A body that is not JSON but parses as XML - a SOAP envelope, an XML API - is carried as the document itself, under __xml:

Not as an array converted from it: that would lose attributes, repeated elements and namespaces, and a trace that no longer matches the document it describes is worth little. The masker looks inside such a string (see "Masking Rules"), so the document is masked in place. This applies in both directions and to both watchers - an incoming request body Laravel does not parse into input(), an outgoing one, and either response.

The sender has to say so. A body is recorded as XML only when its Content-Type is one (application/xml, text/xml, application/soap+xml, *+xml) and it parses as XML. Parsing alone is not enough: an HTML fragment - what an htmx or Turbo endpoint returns - is well-formed markup carrying a CSRF token in value="…", and the masker matches names, so it cannot reach it. A body that is neither JSON nor labelled XML is dropped, as it always was.

A body is also dropped, with {"__skipped": "body_too_large"}, above the size the masker will read (1 MB), and with {"__skipped": "non_utf8_body"} when it is not valid UTF-8 - a trace's data is serialised with json_encode, and invalid bytes there would replace the whole payload of that trace with an encoding error, not just the body.

Large responses are skipped and marked with:

Masking Rules

Masking runs in the dispatcher job, right before a batch is sent, and never in the traced application. Building a trace costs the application only what it takes to collect and hand off the data; walking a payload key by key is paid for by the dispatcher workers instead. Two consequences follow:

Watchers do not mask. What they do at runtime is hide and truncate: only_paths, excepted_paths, hidden_paths, max_content_length, per-watcher excepted lists. The one exception is the database watcher: query bindings are positional, so no key list can reach them and the dispatcher job has nothing to decide by. It masks them itself, by length - a string of more than five characters becomes ********, a shorter one and a numeric binding are recorded as they were. Nothing in a binding says whether it is a password or a page number, so a short or numeric secret - a PIN, an OTP, a card number held as an integer - does reach the receiver. Do not keep those in cleartext columns, or turn the watcher off.

The key lists

Both lists are masks, case-insensitive, with * as a wildcard. A mask is matched against the whole key and against each of its word components — a key is split on _, -, ., : and camelCase boundaries:

Mask Matches Does not match
pass pass, db_pass, smtp_pass, pass_hash passengers, compass, bypass_cache
auth auth, basic_auth, x-auth-user, php-auth-pw author, authorized
token token, api_token, apiToken tokenizer
*token* api_token, tokenizer stock

Neither a substring search nor a whole-key match on its own would do. A substring rule cannot be narrowed once it is too broad: auth also took author, and each match took the whole value and, through inheritance, the subtree under it. A whole-key rule cannot be widened without wildcards that bring the false positives back: auth then stopped matching php-auth-pw — the plaintext password Symfony puts beside the base64 header. Matching a component gives both, and a mask with wildcards is still available for names that are one word.

A match on a parent key applies to its subtree, so auth covers auth.method too - that needs the parent to be one level in, since the top level is not matched at all (see below). A key in both lists is masked whole: the stricter list wins.

Masking is off only when all three lists (value_patterns included) are empty. That switch does not reach the database watcher's bindings: they are positional, no key list can say which one is a password, so the watcher masks them itself, by length, whatever these lists hold.

The split is the point. A secret is worthless the moment any of it leaks, so masking.full_keys replaces the value entirely: ********, a fixed width, so the length of the secret does not leak either. An address, a phone number or a name is mostly there to tell two records apart, so masking.partial_keys keeps two characters at each end - [email protected] becomes jo****************om. Never put a secret in partial_keys: what is left is enough to correlate records, and for a short value it is enough to guess it.

A value that is a string containing a document is parsed, masked and serialised back: applications hand whole documents over as strings - an Eloquent array cast puts one straight into a model's changes, a SOAP call arrives as one - and the key carrying such a string says nothing about what is inside it. Two formats are looked into:

A document in which nothing matched is kept byte for byte.

Whatever the shape, the walk stops at a fixed depth and masks what is left whole. It is a backstop rather than a limit anyone should meet: running out of memory is a fatal error, and a fatal error in the dispatcher job takes the worker with it.

A trace the masker cannot read is replaced, not dropped and not shipped. Its data becomes a single __mask_error key naming what went wrong. Masking is deterministic, so letting the exception out would cost the whole batch and every one of its retries - the traces around the broken one included.

XML entities are never expanded, so a document that arrived from outside cannot make the dispatcher read a local file or unfold a billion-laughs bomb while it is being masked - masking runs in a worker over payloads the application did not write. Two consequences follow, and both fail closed: a document whose values live in an internal DTD is masked whole, because masking around &secret; while leaving its declaration in place reads as protection without being any; and a document carrying declarations that does not parse at all is masked whole for the same reason.

Anything else that merely starts with < - a fragment of prose, a page - is left alone, with the value patterns applied to it as ordinary text.

A value under a query_string key is masked parameter by parameter rather than as a whole, so page=2&api_token=secret keeps the page and loses the token. This is where the request watchers put a url's query string: a url is also a tag and a title, and nothing masks those.

Value patterns

Some things identify a person by their own shape, wherever they turn up, and no key name points at them: an address inside Anonymous:mail,[email protected], or in the middle of a log message. masking.value_patterns are regular expressions matched against the value, and what they match is masked in place - partially, so the rest of the string stays readable:

They are not bound to a key, so unlike the key lists they apply at the top level too, and they reach inside JSON strings and query strings, into array keys, and into a trace's tags - which nothing else masks. A key match still wins: a token holding an address loses all of it, not just the middle. An invalid pattern is dropped rather than raising a warning for every string in every trace.

The key lists and the patterns cover different things and are meant to be used together - recipient in partial_keys catches a phone number under recipients.vonage, which no address pattern would ever match.

The top level of a trace's data is never masked. That level belongs to the watcher, not to the application: connection_name, request, changes, context, bindings and so on are a fixed structure, and the traced data starts one level in. Matching therefore begins inside it - context.customer_email and job.data.customer_email are masked, while connection_name is left readable even though it contains _name. Watchers whose own top level used to hold application data were reshaped so this rule holds for them too: a cache value sits under its cache key (cache.<key>.value, so the key itself is what the list matches against), and mail addresses sit under message as email/full_name pairs.

Widen a mask when the shipped list misses something of yours - *ssn* instead of ssn, *iban* instead of iban - and narrow one when it catches a field you need. Over-masking is the safe direction for telemetry, but it is a choice you make per mask rather than one the package makes for you.

What a key list cannot reach

Some fields are free text, and no key name describes what is inside them. The key lists do not apply to these; only value_patterns do, and only for what has a shape worth matching:

Field What it holds
log.message whatever was logged
dump.dump whatever was dumped - dd($user->api_token) is exactly this
schedule.output the scheduled command's stdout
db.sql, and the sql fragment in a db trace's tags the statement, though its values travel as bindings, which the watcher masks by length

For those, the controls are the watcher's own: turn the watcher off, or keep secrets out of what you log and dump. A trace's tags are in the same position - bare strings with no key naming them - which is why value patterns apply to them too.

Masked values

Masked values keep basic types, so a masked payload stays shaped like the original:

An empty string is left as it is: a mask there would claim something had been hidden.

An array is walked, not replaced. A matching key covers its subtree, so every leaf under it is masked while the structure and the key names survive: {"token":{"a":"secret","b":2}} becomes {"token":{"a":"********","b":0}}. Keys are data too when the application chooses them - a cache key is otp:<address> often enough - so value_patterns are applied to array keys and to tags as well, the two places no key list can reach.

Guzzle / HTTP Client tracing

You can attach the SLogger handler to Guzzle:

Formatters hide and truncate; sensitive values are masked later, by the dispatcher job.

Concurrency

A trace has state while it runs: which trace is the current parent, which parent traces are still open, whether the watchers are paused, what add() said about this request. Where a process handles one request, one job or one command at a time, that state is the process's, and keeping it in fields on a few singletons is correct.

Under a runtime that handles several at once in one process - coroutines, an event loop, fibers - a field is shared by all of them. A second request reads the first one's parent id and files itself underneath it; a request inside a paused section silences the watchers of every other request in flight; one request's user_id is written onto another's traces.

So the state lives in a store, and the store decides what "current" means.

Choosing a store

Check that fiber is the right answer before setting it. It tells units apart by Fiber::getCurrent(), so under a runtime whose coroutines are not PHP fibers - Swoole, or a scheduler outside PHP - it silently degrades to array and nothing is fixed. One line inside a request settles it:

If that is false, write a store instead.

Writing your own store

Two methods, no dependencies:

Then SLOGGER_CONTEXT=App\Tracing\CoroutineTraceContext. Four things it has to honour:

There is deliberately no way to remove a key. A store whose reads fall through to an enclosing unit of work - a coroutine context with inheritance usually does - would uncover the enclosing unit's value; "there is nothing here" is written as null or as an empty list instead.

What fiber does not do

Reads do not fall through to the fiber that created the current one. PHP cannot say which fiber that was, and guessing is how unrelated traces get stitched into one tree.

The consequence is worth knowing before you switch: a fiber started inside a traced request begins with no state of its own, so child traces pushed from inside it are dropped - a query, a log line or an event recorded there has no open parent trace in that fiber, and push() returns early. Parent traces started there are recorded, as roots rather than nested under the request, and so is anything a watcher marks as able to stand alone (can_be_orphan). If your host code runs fibers inside a unit of work (amphp/revolt and anything built on them), fiber will cost you that telemetry; a store that can name the enclosing coroutine will not.

The same applies to an outbound HTTP call whose response is handled somewhere other than where the call was made. Guzzle's response hook runs wherever the promise is resolved: with curl and wait() that is the fiber that made the call, and everything works - but under a runtime that settles promises on a loop fiber of its own, the call is opened in one unit of work and answered in another, and the trace is never closed. This is not about which map the watcher keeps its entry in; the processor's own record of the call lives with the unit that made it either way.

A unit of work that never reports back

Any store that scopes state to a unit of work - this one, or your own - changes what happens when a unit ends without saying so. Worth knowing before you switch.

An exception is not that case: the framework still fires RequestHandled, JobFailed or CommandFinished, so the trace closes as failed and everything below is irrelevant. The case is a unit dropped while suspended - the scheduler killed it, a timeout took it, the process is shutting down.

Nothing the store holds is leaked. The state goes with the unit: PHP unwinds a destroyed fiber's stack, and the store releases its map along with it - the open traces, the parent id, the outbound calls, the values add() collected. (The profiler is not in the store and could not survive this, which is why it is refused outright - see below.)

What is lost is the closing update. The parent trace stays started in the backend, and the outbound calls it left in flight are never tagged __interrupted, because the processor's sweeps only ever see the current unit and no later unit can reach them. With array a later request or job in the same process swept them, which is the one thing this gives up.

It cannot be fixed from inside the package. Cleanup during a fiber's destruction is not allowed to do I/O - Fiber::suspend() throws Cannot suspend in a force-closed fiber from a finally there, and Cannot suspend outside of a fiber from a destructor after it - while dispatching a trace is exactly a suspension point under such a runtime.

Age them out on the receiver instead: a trace with no update for some minutes is over, whatever happened to the process that started it. That is the only place still looking after the process is gone, and it covers a fatal error or an out-of-memory kill too, which nothing running inside the process ever will.

Durations in a long-lived process

LARAVEL_START is defined once, in the entry script, where the process begins. Under php-fpm the process is this request, the bootstrap it measures is this request's, and both boot_time and the duration are counted from it. Under a server that boots once and then serves for hours - Octane, RoadRunner, FrankenPHP's worker mode, a coroutine runtime

So the constant is taken only where it is this request's own start. A process booted from the console and answering HTTP is a long-lived server whatever it says about itself, and never takes it. Under any other SAPI it is spent by the first request the process traces and by no other. Everything else measures from the moment this package's middleware saw the request - taken inside that request's own flow, so it is the request's own - and reports boot_time as -1, since the boot it did not wait through is not its to claim.

Never from Kernel::requestStartedAt(), whichever runtime: it is one field for a whole process, and a request starting alongside replaces it with a later one - a start in the future, which is a negative duration.

Two things this leaves out, both small and both unavoidable from inside a package. Where the constant is not taken, the stretch from entering the kernel to reaching this middleware - the global middleware in front of it - is not counted; a runtime that wants it back has to hand over the moment it accepted the request, because there is no earlier point to read in a worker that booted hours ago (REQUEST_TIME_FLOAT is the process's start there too). And under a worker whose SAPI is not the console, its first request counts a boot it did not wait through: one trace per worker, and nothing that can tell that boot from a slow one.

What is per unit of work, and what is not

Per unit of work: the current parent trace id, the stack of open parent traces, the pause flag, the map of detached (outbound) traces, the requests and commands a parent watcher has open, the outbound calls the HTTP-client watcher has open, the jobs the job watcher is processing, and the values add() collected.

Per process, on purpose: which watchers are enabled, the callbacks registered with add(fn() => ...), and the trace dispatcher's batching buffer - per unit it would stop batching and multiply the jobs.

Those callbacks are shared, which is what makes them rules rather than values - so register them once, from a service provider, and let them read what they need when they run: add('tenant', fn() => tenant()?->id()). A closure registered per request, closing over that request's own objects, is evaluated for every other unit's traces too, and puts one request's data on another's - the very thing the rest of this section is about. A plain value is this unit's own and is safe anywhere.

The socket client is neither: it keeps a small pool of connections - the one it was built with, plus any it opens through Connection::fresh() - and a sender holds one from the first byte written to the last byte read. A process sending one batch at a time opens exactly one and keeps it, as before; concurrent senders each get one of their own rather than interleaving frames on a shared stream, and up to eight are kept for reuse, the rest closed on the way back. None of it is aware of fibers, so it holds for any way of running things at once.

Whether senders can get inside each other's exchange at all is a property of the runtime, not of this package: a bare PHP fiber suspends only where it says so, and Connection says so nowhere. A runtime that turns a stream call into a suspension point - which is what makes coroutines worth having in the first place - can. The pool costs nothing where they cannot.

Two watchers that concurrency does not suit

Upgrading to 2.1

The default behaviour does not change, and neither does any public method. Two constructors did, which matters only if you build one yourself or override one in a subclass:

Class Change
TraceIdContainer now takes TraceContextInterface; it had no constructor before
HttpMiddleware now takes TraceIdContainer as a second argument

Connection gained fresh(), and SocketClient and ApiClientFactory kept their arguments.

Processor, RequestWatcher, CommandWatcher, JobWatcher, HttpClientWatcher and TraceDataComplementer each take a TraceContextInterface as their last argument. All of these are resolved from the container, so nothing else has to change.

Dispatchers

Queue dispatcher

Start the dispatcher (spawns queue workers):

The master keeps every worker slot filled. One that keeps dying on boot is replaced with a growing delay rather than once a second, and a slot counts as settled only once its worker has stayed up a minute. Starting a second dispatcher takes over from the first: it stops the running one, then starts its own fleet.

Stop the dispatcher:

Memory dispatcher

Stores traces in memory only. Intended for tests/local development.

Storage

SLogger does not persist traces locally. The only local files are the dispatcher state file and the lock beside it:

You may want to ignore the folder:

Testing

Run tests:

The testbench config uses in-memory sqlite and memory dispatcher.

Troubleshooting

License

MIT


All versions of laravel with dependencies

PHP Build Version
Package Version
Requires ext-pcntl Version *
ext-posix Version *
laravel/framework Version ^10.26 | ^11.0 | ^12.0
symfony/process Version ^6.4|^7.0
php Version ^8.2
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package slogger/laravel contains the following files

Loading the files please wait ...