Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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')
);
```
61 changes: 0 additions & 61 deletions packages/console/src/Commands/TailCommand.php

This file was deleted.

51 changes: 0 additions & 51 deletions packages/console/src/Commands/TailServerLogCommand.php

This file was deleted.

7 changes: 7 additions & 0 deletions packages/core/src/LogExceptionProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
namespace Tempest\Core;

use Tempest\Debug\Debug;
use Tempest\Log\Logger;
use Throwable;

/**
* An exception processor that logs exceptions.
*/
final class LogExceptionProcessor implements ExceptionProcessor
{
public function __construct(
private readonly Logger $logger,
) {}

public function process(Throwable $throwable): void
{
$items = [
Expand All @@ -21,6 +26,8 @@ public function process(Throwable $throwable): void
: [],
];

$this->logger->error($throwable->getMessage(), $items);

Debug::resolve()->log($items, writeToOut: false);
}
}
1 change: 1 addition & 0 deletions packages/debug/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"minimum-stability": "dev",
"require": {
"php": "^8.4",
"tempest/console": "dev-main",
"tempest/highlight": "^2.11.4",
"symfony/var-dumper": "^7.1"
},
Expand Down
Loading