Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
450ca75
fix(core): gracefully handle missing seeders when using `db:seed` (#1…
xHeaven Dec 3, 2025
cbe54d7
fix(auth): pass scopes/options to auth URL builder (#1750)
xHeaven Dec 3, 2025
5c68b96
fix(auth): update outdated authenticatable import (#1752)
xHeaven Dec 3, 2025
96dad91
refactor(auth): remove double base url call (#1753)
xHeaven Dec 3, 2025
1a2e8fb
feat(cache): make `assertLocked` ensure that the checked lock has an …
xHeaven Dec 3, 2025
e324f6e
fix(cache): add descriptions to `cache:clear` arguments (#1755)
xHeaven Dec 3, 2025
65a1c01
refactor(cache): several fixes and improvements for the cache package…
xHeaven Dec 3, 2025
c5db557
refactor(auth): clear oauth state after validation (#1754)
xHeaven Dec 3, 2025
ee94b81
refactor(debug): correct `dd()` return type and simplify conditionals…
xHeaven Dec 3, 2025
4cb006c
refactor(database): minor cleanup (#1768)
xHeaven Dec 3, 2025
95dcb32
refactor(datetime): fix return type annotation and parameter order (#…
xHeaven Dec 3, 2025
9ac7b95
refactor(log): fix parameter types (#1771)
xHeaven Dec 3, 2025
f53b221
refactor(mail): correct SMTP defaults and config handling (#1772)
xHeaven Dec 3, 2025
9ad1587
fix(process): properly return exit code if missing (#1776)
xHeaven Dec 3, 2025
05165bd
refactor(mapper): improve property and enum handling (#1773)
xHeaven Dec 3, 2025
dc701e3
refactor(support): add validation and fix edge cases in array helpers…
xHeaven Dec 3, 2025
567245f
refactor(validation): add type guards and Unicode support across rule…
xHeaven Dec 3, 2025
288ac6e
refactor(vite-plugin): fix redirect handling and improve error messag…
xHeaven Dec 3, 2025
fdd6b8b
refactor(core): simplify view component resolution (#1783)
xHeaven Dec 3, 2025
c7237dc
refactor(kv-store): fix typo, correct type annotation, and fix TTL ca…
xHeaven Dec 3, 2025
fcf35f2
refactor(icon): minor improvements (#1769)
xHeaven Dec 3, 2025
be93ec1
feat(container): make all container properties publicly readable (#1785)
brendt Dec 4, 2025
6153ffe
chore: release v2.13.0
brendt Dec 4, 2025
f9c246d
chore: post-release clean up
brendt Dec 4, 2025
e8660ae
refactor(logging)!: dissociate `tempest/debug` from `tempest/log`
innocenzi Oct 2, 2025
8cf238a
refactor: simply configuration
innocenzi Oct 2, 2025
ce89985
refactor: revert custom datetime changes
innocenzi Oct 2, 2025
809538f
refactor: use environment as logging prefix
innocenzi Oct 4, 2025
b80cf72
feat: log exceptions
innocenzi Oct 4, 2025
8d1ec02
refactor: clean up and docs
innocenzi Oct 4, 2025
ca8c4b1
style: apply fixes from mago
innocenzi Nov 10, 2025
b633707
fix: remove unused appconfig assignation
innocenzi Nov 19, 2025
7a9ff42
style: apply fixes from mago
innocenzi Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,30 @@

All notable changes to this project will be documented in this file.

## [2.12.0](https://github.com/tempestphp/tempest-framework/compare/v2.11.0..2.12.0) — 2025-11-28
## [2.13.0](https://github.com/tempestphp/tempest-framework/compare/v2.12.0..2.13.0) — 2025-12-04

### 🚀 Features

- **auth**: add OAuth installer (#1674) ([9c82b71](https://github.com/tempestphp/tempest-framework/commit/9c82b715b448633a704591e9b78823da28debc98))
- **cache**: make `assertLocked` ensure that the checked lock has an expiration (#1758) ([1a2e8fb](https://github.com/tempestphp/tempest-framework/commit/1a2e8fbe90259d2bd5a8a0876d4b8fed35c5dcd7))
- **container**: make all container properties publicly readable (#1785) ([be93ec1](https://github.com/tempestphp/tempest-framework/commit/be93ec1388ec7d253637705d4335d13a78a39f00))
- **database**: add support for self-referencing relations (#1745) ([df2dcdc](https://github.com/tempestphp/tempest-framework/commit/df2dcdc231384d2dd359f8b621f0ae1f31a3e703))
- **http**: add support to mark Request properties as #[SensitiveField] (#1746) ([0000c99](https://github.com/tempestphp/tempest-framework/commit/0000c99251b31d0bfa84389fb101be1560d916c3))

### 🐛 Bug fixes

- **auth**: correctly map user in GitHub OAuth provider (#1751) ([ad2182a](https://github.com/tempestphp/tempest-framework/commit/ad2182ac40684b78752e7f7511228688f5093c1a))
- **auth**: pass scopes/options to auth URL builder (#1750) ([cbe54d7](https://github.com/tempestphp/tempest-framework/commit/cbe54d7f3f7e137fe43e9ad7f8837bd2f7103e9a))
- **auth**: update outdated authenticatable import (#1752) ([5c68b96](https://github.com/tempestphp/tempest-framework/commit/5c68b968763229dfb5a78c01a80df3b1b134e6c0))
- **cache**: support enum tags (#1756) ([678b695](https://github.com/tempestphp/tempest-framework/commit/678b69582e526e25ff545c346179bda9636f1415))
- **cache**: add descriptions to `cache:clear` arguments (#1755) ([e324f6e](https://github.com/tempestphp/tempest-framework/commit/e324f6e767b50acd6e76e8310be12422b85e782b))
- **command-bus**: extract uuid from pending commands when not provided (#1761) ([b787c16](https://github.com/tempestphp/tempest-framework/commit/b787c16e57f60de3bd7883944561f02fce3a661a))
- **console**: properly normalize boolean flag names (#1762) ([c6e6867](https://github.com/tempestphp/tempest-framework/commit/c6e6867ede678b9798386bab12e1e2afaef91bc8))
- **core**: gracefully handle missing seeders when using `db:seed` (#1759) ([450ca75](https://github.com/tempestphp/tempest-framework/commit/450ca7576c6e5a8f4f5719dd27e7d4d4a29954c9))
- **process**: properly return exit code if missing (#1776) ([9ad1587](https://github.com/tempestphp/tempest-framework/commit/9ad158747a810db490aef43a7a6c1bcfe062d900))


## [2.12.0](https://github.com/tempestphp/tempest-framework/compare/v2.11.0..v2.12.0) — 2025-11-28

### 🚀 Features

Expand Down Expand Up @@ -798,7 +821,7 @@ All notable changes to this project will be documented in this file.
- rector (#680) ([7fdff1d](https://github.com/tempestphp/tempest-framework/commit/7fdff1d7be48ab91fb35e1a07434ae54ef47781c))


## [1.0.0-alpha.3](https://github.com/tempestphp/tempest-framework/compare/v1.0.0-alpha.2..v1.0.0-alpha.3) — 2024-10-30
## [1.0.0-alpha.3](https://github.com/tempestphp/tempest-framework/compare/v1.0.0-alpha.2..v1.0.0-alpha.3) — 2024-10-31

### 🚨 Breaking changes

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"adam-paterson/oauth2-slack": "^1.1",
"aws/aws-sdk-php": "^3.338.0",
"azure-oss/storage-blob-flysystem": "^1.2",
"brianium/paratest": "^7.14",
"carthage-software/mago": "1.0.0-beta.28",
"depotwarehouse/oauth2-twitch": "^1.3",
"guzzlehttp/psr7": "^2.6.1",
Expand Down Expand Up @@ -87,8 +88,7 @@
"tempest/blade": "dev-main",
"thenetworg/oauth2-azure": "^2.2",
"twig/twig": "^3.16",
"wohali/oauth2-discord-new": "^1.2",
"brianium/paratest": "^7.14"
"wohali/oauth2-discord-new": "^1.2"
},
"replace": {
"tempest/auth": "self.version",
Expand Down
200 changes: 150 additions & 50 deletions docs/2-features/09-logging.md
Original file line number Diff line number Diff line change
@@ -1,96 +1,196 @@
---
title: Logging
description: "Learn how to use Tempest's logging features to monitor and debug your application."
---

Logging is an essential part of any developer's job. Whether it's for debugging or for production monitoring. Tempest has a powerful set of tools to help you access the relevant information you need.
## Overview

## Debug log
Tempest provides a logging implementation built on top of [Monolog](https://github.com/Seldaek/monolog) that follows PSR-3 and the [RFC 5424 specification](https://datatracker.ietf.org/doc/html/rfc5424). This gives you access to eight standard log levels and the ability to send log messages to multiple destinations simultaneously.

First up are Tempest's debug functions: `ld()` (log, die), `lw()` (log, write), and `ll()` (log, log). These three functions are similar to Symfony's var dumper and Laravel's `dd()`, although there's an important difference.
The system supports file logging, Slack integration, system logs, and custom channels. You can configure different loggers for different parts of your application using Tempest's [tagged singletons](../1-essentials/05-container.md#tagged-singletons) feature.

You can think of `ld()` or `lw()` as Laravel's `dd()` and `dump()` variants. In fact, Tempest uses Symfony's var-dumper under the hood, just like Laravel. Furthermore, if you haven't installed Tempest in a project that already includes Laravel, Tempest will also provide `dd()` and `dump()` as aliases to `ld()` and `lw()`.
## Writing logs

The main difference is that Tempest's debug functions will **also write to the debug log**, which can be tailed with tempest's built-in `tail` command. This is its default output:
To start logging messsages, you may inject the {b`Tempest\Log\Logger`} interface in any class. By default, log messages will be written to a daily rotating log file stored in `.tempest/logs`. This may be customized by providing a different [logging configuration](#configuration).

```console
./tempest tail
```php app/Services/UserService.php
use Tempest\Log\Logger;

<h2>Project</h2> Listening at /Users/brent/Dev/tempest-docs/log/tempest.log
<h2>Server</h2> <error>No server log configured in LogConfig</error>
<h2>Debug</h2> Listening at /Users/brent/Dev/tempest-docs/log/debug.log
final readonly class UserService
{
public function __construct(
private Logger $logger,
) {}
}
```

Wherever you call `ld()` or `lw()` from, the output will also be written to the debug log, and tailed automatically with the `./tempest tail` command. On top of that, `tail` also monitors two other logs:
Tempest supports all eight levels described in the [RFC 5424](https://tools.ietf.org/html/rfc5424) specification. It is possible to configure channels to only log messages at or above a certain level.

- The **project log**, which contains everything the default logger writes to
- The **server log**, which should be manually configured in `LogConfig`:
```php
$logger->emergency('System is unusable');
$logger->alert('Action required immediately');
$logger->critical('Important, unexpected error');
$logger->error('Runtime error that should be monitored');
$logger->warning('Exceptional occurrence that is not an error');
$logger->notice('Uncommon event');
$logger->info('Miscellaneous event');
$logger->debug('Detailed debug information');
```

### Providing context

All log methods accept an optional context array for additional information. This data is formatted as JSON and included with your log message:

```php
// app/Config/log.config.php
$logger->error('Order processing failed', context: [
'user_id' => $order->userId,
'order_id' => $order->id,
'total_amount' => $order->total,
'payment_method' => $order->paymentMethod,
'error_code' => $exception->getCode(),
'error_message' => $exception->getMessage(),
]);
```

use Tempest\Log\LogConfig;
## Configuration

return new LogConfig(
serverLogPath: '/path/to/nginx.log'
By default, Tempest uses a daily rotating log configuration that creates a new log file each day and retains up to 31 files:

// …
```php config/logging.config.php
use Tempest\Log\Config\DailyLogConfig;
use Tempest;

return new DailyLogConfig(
path: Tempest\internal_storage_path('logs', 'tempest.log'),
maxFiles: Tempest\env('LOG_MAX_FILES', default: 31)
);
```

If you're only interested in tailing one or more specific logs, you can filter the `tail` output like so:
To configure a different logging channel, you may create a `logging.config.php` file anywhere and return one of the [available configuration classes](#available-configurations-and-channels).

### Specifying a minimum log level

Every configuration class and log channel accept a `minimumLogLevel` property, which defines the lowest severity level that will be logged. Messages below this level will be ignored.

```console
./tempest tail --debug
```php config/logging.config.php
use Tempest\Log\Config\MultipleChannelsLogConfig;
use Tempest\Log\Channels\DailyLogChannel;
use Tempest\Log\Channels\SlackLogChannel;
use Tempest;

<h2>Debug</h2> Listening at /Users/brent/Dev/tempest-docs/log/debug.log
return new MultipleChannelsLogConfig(
channels: [
new DailyLogChannel(
path: Tempest\internal_storage_path('logs', 'tempest.log'),
maxFiles: Tempest\env('LOG_MAX_FILES', default: 31),
minimumLogLevel: LogLevel::DEBUG,
),
new SlackLogChannel(
webhookUrl: Tempest\env('SLACK_LOGGING_WEBHOOK_URL'),
channelId: '#alerts',
minimumLogLevel: LogLevel::CRITICAL,
),
],
);
```

Finally, the `ll()` function will do exactly the same as `lw()`, but **only write to the debug log, and not output anything in the browser or terminal**.
### Using multiple loggers

## Logging channels
In situations where you would like to log different types of information to different places, you may create multiple tagged configurations to create separate loggers for different purposes.

On top of debug logging, Tempest includes a monolog implementation which allows you to log to one or more channels. Writing to the logger is as simple as injecting `\Tempest\Log\Logger` wherever you'd like:
For instance, you could have a logger dedicated to critical alerts, while each of your application's module have its own logger:

```php
// app/Rss.php
```php src/Monitoring/logging.config.php
use Tempest\Log\Config\DailyLogConfig;
use Modules\Monitoring\Logging;
use Tempest;

return new SlackLogConfig(
webhookUrl: Tempest\env('SLACK_LOGGING_WEBHOOK_URL'),
channelId: '#alerts',
minimumLogLevel: LogLevel::CRITICAL,
tag: Logging::SLACK,
);
```

```php src/Orders/logging.config.php
use Tempest\Log\Config\DailyLogConfig;
use Modules\Monitoring\Logging;
use Tempest;

use Tempest\Console\Console;
use Tempest\Console\ConsoleCommand;
return new DailyLogConfig(
path: Tempest\internal_storage_path('logs', 'orders.log'),
tag: Logging::ORDERS,
);
```

Using this approach, you can inject the appropriate logger using [tagged singletons](../1-essentials/05-container.md#tagged-singletons). This gives you the flexibility to customize logging behavior in different parts of your application.

```php src/Orders/ProcessOrder.php
use Tempest\Log\Logger;

final readonly class Rss
final readonly class ProcessOrder
{
public function __construct(
private Console $console,
#[Tag(Logging::ORDERS)]
private Logger $logger,
) {}

#[ConsoleCommand]
public function sync()
public function __invoke(Order $order): void
{
$this->logger->info('Starting RSS sync');

//
$this->logger->info('Processing new order', ['order' => $order]);
// ...
}
}
```

If you're familiar with [monolog](https://seldaek.github.io/monolog/), you know how it supports multiple handlers to handle a log message. Tempest adds a small layer on top of these handlers called channels, they can be configured within `LogConfig`:
### Available configurations and channels

```php
// app/Config/log.config.php
Tempest provides a few log channels that correspond to common logging needs:

use Tempest\Log\LogConfig;
use Tempest\Log\Channels\AppendLogChannel;
- {b`Tempest\Log\Channel\AppendLogChannel`} — append all messages to a single file without rotation,
- {b`Tempest\Log\Channel\DailyLogChannel`} — create a new file each day and remove old files automatically,
- {b`Tempest\Log\Channel\WeeklyLogChannel`} — create a new file each week and remove old files automatically,
- {b`Tempest\Log\Channel\SlackLogChannel`} — send messages to a Slack channel via webhook,
- {b`Tempest\Log\Channel\SysLogChannel`} — write messages to the system log.

return new LogConfig(
channels: [
new AppendLogChannel(path: __DIR__ . '/../log/project.log'),
]
);
```
As a convenient abstraction, a configuration class for each channel is provided:

- {b`Tempest\Log\Config\SimpleLogConfig`}
- {b`Tempest\Log\Config\DailyLogConfig`}
- {b`Tempest\Log\Config\WeeklyLogConfig`}
- {b`Tempest\Log\Config\SlackLogConfig`}
- {b`Tempest\Log\Config\SysLogConfig`}

These configuration classes also accept a `channels` property, which allows for providing multiple channels for a single logger. Alternatively, you may use the {b`Tempest\Log\Config\MultipleChannelsLogConfig`} configuration class to achieve the same result more explicitly.

**Please note:**
## Debugging

- Currently, Tempest only supports the `AppendLogChannel` and `DailyLogChannel`, but we're adding more channels in the future. You can always add your own channels by implementing `\Tempest\Log\LogChannel`.
- Also, it's currently not possible to configure environment-specific logging channels, this we'll also support in the future. Again, you're free to make your own channels that take the current environment into account.
Tempest includes several global functions for debugging. Typically, these functions are for quick debugging and should not be committed to production.

- `ll()` — writes values to the debug log without displaying them,
- `lw()` (also `dump()`) — logs values and displays them,
- `ld()` (also `dd()`) — logs values, displays them, and stops execution,
- `le()` — logs values and emits an {b`Tempest\Debug\ItemsDebugged`} event.

### Tailing debug logs

Debug logs are written with console formatting, so they can be tailed with syntax highlighting. You may use `./tempest tail:debug` to monitor the debug log in real time.

:::warning
By default, debug logs are cleared every time the `tail:debug` command is run. If you want to keep previous log entries, you may pass the `--no-clear` flag.
:::

### Configuring the debug log

By default, the debug log is written to `.tempest/debug.log`. This is configurable by creating a `debug.config.php` file that returns a {b`Tempest\Debug\DebugConfig`} with a different `path`:

```php config/debug.config.php
use Tempest\Debug\DebugConfig;
use Tempest;

return new DebugConfig(
logPath: Tempest\internal_storage_path('logs', 'debug.log')
);
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace Tempest\Auth\Exceptions;

use Exception;
use Tempest\Auth\Authenticatable;
use Tempest\Auth\Authentication\Authenticatable;

final class AuthenticatableModelWasInvalid extends Exception implements AuthenticationException
{
Expand Down
9 changes: 7 additions & 2 deletions packages/auth/src/OAuth/GenericOAuthClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public function buildAuthorizationUrl(array $scopes = [], array $options = []):

public function createRedirect(array $scopes = [], array $options = []): Redirect
{
$to = $this->buildAuthorizationUrl();
$to = $this->buildAuthorizationUrl($scopes, $options);

$this->session->set($this->sessionKey, $this->provider->getState());

Expand Down Expand Up @@ -100,7 +100,12 @@ public function fetchUser(AccessToken $token): OAuthUser

public function authenticate(Request $request, Closure $map): Authenticatable
{
if ($this->session->get($this->sessionKey) !== $request->get('state')) {
$expectedState = $this->session->get($this->sessionKey);
$actualState = $request->get('state');

$this->session->remove($this->sessionKey);

if ($expectedState !== $actualState) {
throw new OAuthStateWasInvalid();
}

Expand Down
14 changes: 12 additions & 2 deletions packages/auth/src/OAuth/Testing/TestingOAuthClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PHPUnit\Framework\Assert;
use Tempest\Auth\Authentication\Authenticatable;
use Tempest\Auth\Authentication\Authenticator;
use Tempest\Auth\Exceptions\OAuthStateWasInvalid;
use Tempest\Auth\OAuth\OAuthClient;
use Tempest\Auth\OAuth\OAuthConfig;
use Tempest\Auth\OAuth\OAuthUser;
Expand Down Expand Up @@ -64,11 +65,11 @@ public function buildAuthorizationUrl(array $scopes = [], array $options = []):
$this->state = Random\secure_string(16);

$provider = $this->config->createProvider();
$provider->getBaseAuthorizationUrl();
$baseAuthorizationUrl = $provider->getBaseAuthorizationUrl();

$url = sprintf(
'%s/oauth/authorize?redirect_uri=%s&client_id=%s&state=%s',
$this->baseUrl ?? $provider->getBaseAuthorizationUrl(),
$this->baseUrl ?? $baseAuthorizationUrl,
$this->redirectUri,
$this->clientId,
$this->state,
Expand Down Expand Up @@ -124,6 +125,15 @@ public function createRedirect(array $scopes = [], array $options = []): Redirec

public function authenticate(Request $request, Closure $map): Authenticatable
{
$expectedState = $this->state;
$actualState = $request->get('state');

$this->state = null;

if ($expectedState !== $actualState) {
throw new OAuthStateWasInvalid();
}

$user = $this->fetchUser($this->requestAccessToken($request->get('code')));

$authenticatable = $map($user);
Expand Down
4 changes: 2 additions & 2 deletions packages/cache/src/Commands/CacheClearCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ public function __construct(
public function __invoke(
#[ConsoleArgument(description: 'Name of the tagged cache to clear')]
?string $tag = null,
#[ConsoleCommand(description: 'Whether to clear all caches')]
#[ConsoleArgument(description: 'Whether to clear all caches')]
bool $all = false,
#[ConsoleCommand(description: 'Whether to clear internal caches')]
#[ConsoleArgument(description: 'Whether to clear internal caches')]
bool $internal = false,
): void {
if (! $this->container instanceof GenericContainer) {
Expand Down
2 changes: 1 addition & 1 deletion packages/cache/src/Commands/CacheStatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function __construct(
public function __invoke(bool $internal = true): void
{
if (! $this->container instanceof GenericContainer) {
$this->console->error('Clearing caches is only available when using the default container.');
$this->console->error('Checking cache status is only available when using the default container.');
return;
}

Expand Down
Loading