betrieb: Logs je Thema in Tagesdateien (billing, ki, monitoring, market); Doku: Duplicate-Content-Decision durch Anhang 2 abgelöst

- config/logging.php: Themen-Channels billing, ki, monitoring, market als
  daily-Dateien mit gemeinsamer Aufbewahrung (LOG_DAILY_DAYS), Stack-Default
  auf daily; Channel `testing` für die Suite.
- Fachliche Log-Aufrufe in Billing, KI-Jobs, OperationalAlert und
  Kursdaten auf Log::channel() umgestellt.
- phpunit.xml + TestCase: Suite schreibt ausschließlich nach
  testing-YYYY-MM-DD.log; LogChannelsTest sichert die Konfiguration.
- docs/weiteres: Decision-Update Duplicate-Content entfernt (überholt durch
  Roadmap-Anhang 2), Decision-Update Verlinkung & Backlinks als Referenz
  wieder abgelegt, README-Index nachgezogen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Kevin Adametz 2026-09-18 13:35:42 +00:00
parent b8c6af5f10
commit 2ec61697b0
21 changed files with 338 additions and 107 deletions

View file

@ -0,0 +1,69 @@
<?php
use App\Services\Web\MarketDataService;
use App\Support\OperationalAlert;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Psr\Log\LoggerInterface;
/**
* Log-Aufteilung: je Thema eine Tagesdatei, das allgemeine Log bleibt für
* Exceptions und Unzugeordnetes. Die Suite selbst schreibt nur nach
* testing-*.log (phpunit.xml + TestCase::setUp).
*/
test('every topic channel writes its own daily file with the shared retention', function (string $channel, string $file) {
// TestCase biegt die Themen-Channels für die Suite um — hier zählt die
// ausgelieferte Konfiguration aus config/logging.php.
$config = require base_path('config/logging.php');
expect($config['channels'][$channel])
->toMatchArray([
'driver' => 'daily',
'path' => storage_path("logs/{$file}.log"),
'days' => 14,
]);
})->with([
'billing' => ['billing', 'billing'],
'ki' => ['ki', 'ki'],
'monitoring' => ['monitoring', 'monitoring'],
'market' => ['market', 'market'],
'admin_slow' => ['admin_slow', 'admin-slow'],
]);
test('the default stack rotates daily instead of growing one single file', function () {
$config = require base_path('config/logging.php');
expect($config['channels']['stack']['channels'])->toBe(['daily'])
->and($config['channels']['daily']['path'])->toBe(storage_path('logs/laravel.log'));
});
test('the test suite logs into the dedicated testing channel only', function () {
expect(config('logging.default'))->toBe('testing')
->and(config('logging.channels.testing.path'))->toBe(storage_path('logs/testing.log'));
foreach (['billing', 'ki', 'monitoring', 'market'] as $channel) {
expect(config("logging.channels.{$channel}.path"))->toBe(storage_path('logs/testing.log'));
}
});
test('operational alerts go to the monitoring channel', function () {
config()->set('monitoring.alerts.enabled', false);
$monitoringLog = Mockery::spy(LoggerInterface::class);
Log::shouldReceive('channel')->once()->with('monitoring')->andReturn($monitoringLog);
OperationalAlert::send('Bounce-Postfach konnte nicht gelesen werden');
$monitoringLog->shouldHaveReceived('critical')
->withArgs(fn (string $message): bool => str_contains($message, 'Bounce-Postfach'))
->once();
});
test('market data failures go to the market channel', function () {
Http::fake(['*' => Http::response('', 500)]);
$marketLog = Mockery::spy(LoggerInterface::class);
Log::shouldReceive('channel')->with('market')->andReturn($marketLog);
app(MarketDataService::class)->refresh();
$marketLog->shouldHaveReceived('warning')->atLeast()->once();
});

View file

@ -6,16 +6,18 @@ use Illuminate\Contracts\Queue\Job;
use Illuminate\Queue\Events\JobFailed;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Psr\Log\LoggerInterface;
test('an alert always logs critical and mails the configured recipient', function () {
config()->set('monitoring.alerts.enabled', true);
config()->set('monitoring.alerts.mail_to', 'ops@example.com');
Mail::fake();
Log::spy();
$monitoringLog = Mockery::spy(LoggerInterface::class);
Log::shouldReceive('channel')->with('monitoring')->andReturn($monitoringLog);
OperationalAlert::send('Geplanter Task fehlgeschlagen: billing:generate-manual-invoices', ['exit_code' => 1]);
Log::shouldHaveReceived('critical')->once();
$monitoringLog->shouldHaveReceived('critical')->once();
Mail::assertSent(OperationalAlertMail::class, function (OperationalAlertMail $mail) {
return $mail->hasTo('ops@example.com')
&& str_contains($mail->summary, 'billing:generate-manual-invoices')

View file

@ -23,5 +23,23 @@ abstract class TestCase extends BaseTestCase
config('database.default'),
));
}
$this->routeTopicLogChannelsToTestingLog();
}
/**
* Die Themen-Channels (`Log::channel('billing')` usw.) umgehen den
* Default-Channel aus phpunit.xml und würden sonst in die echten
* Themen-Dateien unter storage/logs schreiben. Alles, was die Suite loggt,
* gehört nach testing-YYYY-MM-DD.log. `admin_slow` bleibt außen vor, weil
* seine Tests den Pfad selbst setzen bzw. prüfen.
*/
private function routeTopicLogChannelsToTestingLog(): void
{
$testing = config('logging.channels.testing');
foreach (['billing', 'ki', 'monitoring', 'market'] as $channel) {
config()->set("logging.channels.{$channel}", $testing);
}
}
}