Skip to content

Commit 8529e46

Browse files
committed
refactor: clean up and docs
1 parent fb03538 commit 8529e46

18 files changed

+421
-114
lines changed

docs/2-features/09-logging.md

Lines changed: 150 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,196 @@
11
---
22
title: Logging
3+
description: "Learn how to use Tempest's logging features to monitor and debug your application."
34
---
45

5-
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.
6+
## Overview
67

7-
## Debug log
8+
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.
89

9-
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.
10+
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.
1011

11-
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()`.
12+
## Writing logs
1213

13-
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:
14+
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).
1415

15-
```console
16-
./tempest tail
16+
```php app/Services/UserService.php
17+
use Tempest\Log\Logger;
1718

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

23-
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:
27+
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.
2428

25-
- The **project log**, which contains everything the default logger writes to
26-
- The **server log**, which should be manually configured in `LogConfig`:
29+
```php
30+
$logger->emergency('System is unusable');
31+
$logger->alert('Action required immediately');
32+
$logger->critical('Important, unexpected error');
33+
$logger->error('Runtime error that should be monitored');
34+
$logger->warning('Exceptional occurrence that is not an error');
35+
$logger->notice('Uncommon event');
36+
$logger->info('Miscellaneous event');
37+
$logger->debug('Detailed debug information');
38+
```
39+
40+
### Providing context
41+
42+
All log methods accept an optional context array for additional information. This data is formatted as JSON and included with your log message:
2743

2844
```php
29-
// app/Config/log.config.php
45+
$logger->error('Order processing failed', context: [
46+
'user_id' => $order->userId,
47+
'order_id' => $order->id,
48+
'total_amount' => $order->total,
49+
'payment_method' => $order->paymentMethod,
50+
'error_code' => $exception->getCode(),
51+
'error_message' => $exception->getMessage(),
52+
]);
53+
```
3054

31-
use Tempest\Log\LogConfig;
55+
## Configuration
3256

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

36-
// …
59+
```php config/logging.config.php
60+
use Tempest\Log\Config\DailyLogConfig;
61+
use Tempest;
62+
63+
return new DailyLogConfig(
64+
path: Tempest\internal_storage_path('logs', 'tempest.log'),
65+
maxFiles: Tempest\env('LOG_MAX_FILES', default: 31)
3766
);
3867
```
3968

40-
If you're only interested in tailing one or more specific logs, you can filter the `tail` output like so:
69+
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).
70+
71+
### Specifying a minimum log level
72+
73+
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.
4174

42-
```console
43-
./tempest tail --debug
75+
```php config/logging.config.php
76+
use Tempest\Log\Config\MultipleChannelsLogConfig;
77+
use Tempest\Log\Channels\DailyLogChannel;
78+
use Tempest\Log\Channels\SlackLogChannel;
79+
use Tempest;
4480

45-
<h2>Debug</h2> Listening at /Users/brent/Dev/tempest-docs/log/debug.log
81+
return new MultipleChannelsLogConfig(
82+
channels: [
83+
new DailyLogChannel(
84+
path: Tempest\internal_storage_path('logs', 'tempest.log'),
85+
maxFiles: Tempest\env('LOG_MAX_FILES', default: 31),
86+
minimumLogLevel: LogLevel::DEBUG,
87+
),
88+
new SlackLogChannel(
89+
webhookUrl: Tempest\env('SLACK_LOGGING_WEBHOOK_URL'),
90+
channelId: '#alerts',
91+
minimumLogLevel: LogLevel::CRITICAL,
92+
),
93+
],
94+
);
4695
```
4796

48-
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**.
97+
### Using multiple loggers
4998

50-
## Logging channels
99+
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.
51100

52-
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:
101+
For instance, you could have a logger dedicated to critical alerts, while each of your application's module have its own logger:
53102

54-
```php
55-
// app/Rss.php
103+
```php src/Monitoring/logging.config.php
104+
use Tempest\Log\Config\DailyLogConfig;
105+
use Modules\Monitoring\Logging;
106+
use Tempest;
107+
108+
return new SlackLogConfig(
109+
webhookUrl: Tempest\env('SLACK_LOGGING_WEBHOOK_URL'),
110+
channelId: '#alerts',
111+
minimumLogLevel: LogLevel::CRITICAL,
112+
tag: Logging::SLACK,
113+
);
114+
```
115+
116+
```php src/Orders/logging.config.php
117+
use Tempest\Log\Config\DailyLogConfig;
118+
use Modules\Monitoring\Logging;
119+
use Tempest;
56120

57-
use Tempest\Console\Console;
58-
use Tempest\Console\ConsoleCommand;
121+
return new DailyLogConfig(
122+
path: Tempest\internal_storage_path('logs', 'orders.log'),
123+
tag: Logging::ORDERS,
124+
);
125+
```
126+
127+
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.
128+
129+
```php src/Orders/ProcessOrder.php
59130
use Tempest\Log\Logger;
60131

61-
final readonly class Rss
132+
final readonly class ProcessOrder
62133
{
63134
public function __construct(
64-
private Console $console,
135+
#[Tag(Logging::ORDERS)]
65136
private Logger $logger,
66137
) {}
67138

68-
#[ConsoleCommand]
69-
public function sync()
139+
public function __invoke(Order $order): void
70140
{
71-
$this->logger->info('Starting RSS sync');
72-
73-
//
141+
$this->logger->info('Processing new order', ['order' => $order]);
142+
143+
// ...
74144
}
75145
}
76146
```
77147

78-
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`:
148+
### Available configurations and channels
79149

80-
```php
81-
// app/Config/log.config.php
150+
Tempest provides a few log channels that correspond to common logging needs:
82151

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

86-
return new LogConfig(
87-
channels: [
88-
new AppendLogChannel(path: __DIR__ . '/../log/project.log'),
89-
]
90-
);
91-
```
158+
As a convenient abstraction, a configuration class for each channel is provided:
159+
160+
- {b`Tempest\Log\Config\SimpleLogConfig`}
161+
- {b`Tempest\Log\Config\DailyLogConfig`}
162+
- {b`Tempest\Log\Config\WeeklyLogConfig`}
163+
- {b`Tempest\Log\Config\SlackLogConfig`}
164+
- {b`Tempest\Log\Config\SysLogConfig`}
165+
166+
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.
92167

93-
**Please note:**
168+
## Debugging
94169

95-
- 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`.
96-
- 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.
170+
Tempest includes several global functions for debugging. Typically, these functions are for quick debugging and should not be committed to production.
171+
172+
- `ll()` — writes values to the debug log without displaying them,
173+
- `lw()` (also `dump()`) — logs values and displays them,
174+
- `ld()` (also `dd()`) — logs values, displays them, and stops execution,
175+
- `le()` — logs values and emits an {b`Tempest\Debug\ItemsDebugged`} event.
176+
177+
### Tailing debug logs
178+
179+
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.
180+
181+
:::warning
182+
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.
183+
:::
184+
185+
### Configuring the debug log
186+
187+
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`:
188+
189+
```php config/debug.config.php
190+
use Tempest\Debug\DebugConfig;
191+
use Tempest;
192+
193+
return new DebugConfig(
194+
logPath: Tempest\internal_storage_path('logs', 'debug.log')
195+
);
196+
```

packages/debug/src/TailDebugCommand.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public function __construct(
2222
) {}
2323

2424
#[ConsoleCommand('tail:debug', description: 'Tails the debug log', aliases: ['debug:tail'])]
25-
public function __invoke(bool $clear = false): void
25+
public function __invoke(bool $clear = true): void
2626
{
2727
$debugLogPath = $this->debugConfig->logPath;
2828

packages/log/src/Channels/AppendLogChannel.php

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@
2020
* @param null|int $filePermission Optional file permissions (default (0644) are only for owner read/write).
2121
*/
2222
public function __construct(
23-
private string $path,
24-
private bool $useLocking = false,
25-
private LogLevel $minimumLogLevel = LogLevel::DEBUG,
26-
private bool $bubble = true,
27-
private ?int $filePermission = null,
23+
private(set) string $path,
24+
private(set) bool $useLocking = false,
25+
private(set) LogLevel $minimumLogLevel = LogLevel::DEBUG,
26+
private(set) bool $bubble = true,
27+
private(set) ?int $filePermission = null,
2828
) {}
2929

3030
public function getHandlers(Level $level): array
@@ -50,9 +50,4 @@ public function getProcessors(): array
5050
new PsrLogMessageProcessor(),
5151
];
5252
}
53-
54-
public function getPath(): string
55-
{
56-
return $this->path;
57-
}
5853
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Tempest\Log\Channels\Slack;
4+
5+
enum PresentationMode
6+
{
7+
/**
8+
* Shows the log message in-line without any formatting.
9+
*/
10+
case INLINE;
11+
12+
/**
13+
* Shows the log message as a Slack block.
14+
*/
15+
case BLOCKS;
16+
17+
/**
18+
* Shows the log message as a Slack block, including any context information.
19+
*/
20+
case BLOCKS_WITH_CONTEXT;
21+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tempest\Log\Channels;
6+
7+
use Monolog\Handler\SlackWebhookHandler;
8+
use Monolog\Level;
9+
use Monolog\Processor\PsrLogMessageProcessor;
10+
use Tempest\Log\Channels\Slack\PresentationMode;
11+
use Tempest\Log\LogChannel;
12+
use Tempest\Log\LogLevel;
13+
14+
final readonly class SlackLogChannel implements LogChannel
15+
{
16+
/**
17+
* @param string $webhookUrl The Slack Incoming Webhook URL.
18+
* @param string|null $channelId The Slack channel ID to send messages to. If null, the default channel configured in the webhook will be used.
19+
* @param string|null $username The username to display as the sender of the message.
20+
* @param PresentationMode $mode The display mode for the Slack messages.
21+
* @param LogLevel $minimumLogLevel The minimum log level to record.
22+
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not.
23+
*/
24+
public function __construct(
25+
private(set) string $webhookUrl,
26+
private(set) ?string $channelId = null,
27+
private(set) ?string $username = null,
28+
private(set) PresentationMode $mode = PresentationMode::INLINE,
29+
private LogLevel $minimumLogLevel = LogLevel::DEBUG,
30+
private(set) bool $bubble = true,
31+
) {}
32+
33+
public function getHandlers(Level $level): array
34+
{
35+
if (! $this->minimumLogLevel->includes(LogLevel::fromMonolog($level))) {
36+
return [];
37+
}
38+
39+
return [
40+
new SlackWebhookHandler(
41+
webhookUrl: $this->webhookUrl,
42+
channel: $this->channelId,
43+
username: $this->username,
44+
level: $level,
45+
useAttachment: $this->mode === PresentationMode::BLOCKS || $this->mode === PresentationMode::BLOCKS_WITH_CONTEXT,
46+
includeContextAndExtra: $this->mode === PresentationMode::BLOCKS_WITH_CONTEXT,
47+
),
48+
];
49+
}
50+
51+
public function getProcessors(): array
52+
{
53+
return [
54+
new PsrLogMessageProcessor(),
55+
];
56+
}
57+
}

0 commit comments

Comments
 (0)