分享一组比 laravel 调度事件中 AppendOutputTo 方法更为便捷的宏方法

特性

  • 默认根据命令名称自动生成输出日志文件路径
  • 默认输出日志文件路径格式为 storage/logs/schedules/命令名称/命令名称.log
  • 由 single 渠道日志自动记录命令运行的开始运行时间,这样就会形成 laravel 标准的日志文件,可以更好的由第三方扩展去解析查看,例如 opcodesio/log-viewer
  • 更为便捷的一组按年、季、月、周、日切割输出日志的方法

源码 - app/Support/Macros/SchedulingEventMacro.php

<?php

declare(strict_types=1);

namespace App\Support\Macros;

use Illuminate\Console\Scheduling\Event;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Stringable;

/**
 * @mixin Event
 *
 * @property $channels
 */
class SchedulingEventMacro
{
    public function userAppendOutputToDaily(): callable
    {
        return fn (
            ?string $filename = null,
            ?string $dirname = null
        ): Event => $this->userAppendOutputTo($filename, sprintf('daily-%s', date('Y-m-d')), $dirname);
    }

    public function userAppendOutputToWeekly(): callable
    {
        return fn (
            ?string $filename = null,
            ?string $dirname = null
        ): Event => $this->userAppendOutputTo($filename, sprintf('weekly-%s', date('Y-W')), $dirname);
    }

    public function userAppendOutputToMonthly(): callable
    {
        return fn (
            ?string $filename = null,
            ?string $dirname = null
        ): Event => $this->userAppendOutputTo($filename, sprintf('monthly-%s', date('Y-m')), $dirname);
    }

    public function userAppendOutputToQuarterly(): callable
    {
        return fn (
            ?string $filename = null,
            ?string $dirname = null
        ): Event => $this->userAppendOutputTo(
            $filename,
            sprintf('quarterly-%s-%s', date('Y'), now()->quarter),
            $dirname
        );
    }

    public function userAppendOutputToYearly(): callable
    {
        return fn (
            ?string $filename = null,
            ?string $dirname = null
        ): Event => $this->userAppendOutputTo($filename, sprintf('yearly-%s', date('Y')), $dirname);
    }

    public function userAppendOutputTo(): callable
    {
        return function (?string $filename = null, ?string $suffix = null, ?string $dirname = null): Event {
            $outputPath = value(
                function (?string $filename, ?string $suffix, ?string $dirname): string {
                    $filename = value(
                        function (?string $filename): string {
                            if ($filename) {
                                return $filename;
                            }

                            // artisan
                            if (str($this->command)->contains("'artisan'")) {
                                $commands = (array) explode(' ', $this->command);

                                return $commands[array_search("'artisan'", $commands, true) + 1];
                            }

                            /** @see \Illuminate\Console\Scheduling\CallbackEvent::withoutOverlapping */
                            if (empty($this->description)) {
                                throw new \LogicException(
                                    "Please incoming the \$filename parameter, Or use the 'name' method before 'userAppendOutputTo'."
                                );
                            }

                            // exec|call|job
                            return $this->description;
                        },
                        $filename
                    );

                    $normalizedFilename = str($filename)->replace([\DIRECTORY_SEPARATOR, '\\', ' '], ['-', '-', '-']);

                    return (
                        $dirname
                            ? str($dirname)
                            : str(storage_path('logs'))
                                ->finish(\DIRECTORY_SEPARATOR)
                                ->append('schedules')
                                ->finish(\DIRECTORY_SEPARATOR)
                                ->append($normalizedFilename)
                    )
                        ->finish(\DIRECTORY_SEPARATOR)
                        ->append($normalizedFilename)
                        ->when(
                            $suffix,
                            static fn (
                                Stringable $stringable,
                                string $suffix
                            ) => $stringable->finish('-')->finish($suffix)
                        )
                        ->append('.log')
                        ->toString();
                },
                $filename,
                $suffix,
                $dirname,
            );

            // dump($outputPath);

            return $this
                ->before(function () use ($outputPath): void {
                    $singleLogPath = config('logging.channels.single.path');
                    $unsetSingleChannelHandler = function (): void {
                        unset($this->channels['single']);
                    };

                    config()->set('logging.channels.single.path', $outputPath);
                    $unsetSingleChannelHandler->call(app('log'));

                    Log::channel('single')->info('>>>>>>>>');

                    config()->set('logging.channels.single.path', $singleLogPath);
                    $unsetSingleChannelHandler->call(app('log'));
                })
                ->appendOutputTo($outputPath);
        };
    }
}

注册

<?php

declare(strict_types=1);

namespace App\Providers;

use App\Support\Macros\SchedulingEventMacro;
use Illuminate\Console\Scheduling\Event;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Event::mixin($this->app->make(SchedulingEventMacro::class));
    }
}

使用

<?php

declare(strict_types=1);

namespace App\Console;

use App\Console\Commands\CpSyncDataCommand;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
use Spatie\ScheduleMonitor\Models\MonitoredScheduledTaskLogItem;

class Kernel extends ConsoleKernel
{
    /**
     * Define the application's command schedule.
     */
    protected function schedule(Schedule $schedule): void
    {
        $schedule->command('model:prune', ['--model' => MonitoredScheduledTaskLogItem::class])->daily()->userAppendOutputToMonthly()->withoutOverlapping();
        $schedule->command('telescope:prune')->daily()->skip($this->app->isProduction())->userAppendOutputToMonthly()->withoutOverlapping();
        $schedule->command(CpSyncDataCommand::class)->hourly()->userAppendOutputToDaily()->withoutOverlapping();
    }
}

效果 - storage/logs/schedules/cp:sync-data/cp:sync-data-daily-2024-01-16.log

[2024-01-16 00:00:03] testing.INFO: >>>>>>>> {"php-version":"8.1.26","php-interface":"cli","laravel-version":"10.40.0","running-in-console":true,"X-Request-Id":"68a4470b-d6b4-4bb5-aa8e-feb8c4c43e29","command":"'/usr/bin/php8.1' 'artisan' cp:sync-data"} 
Your Command output...
[2024-01-16 01:00:02] testing.INFO: >>>>>>>> {"php-version":"8.1.26","php-interface":"cli","laravel-version":"10.40.0","running-in-console":true,"X-Request-Id":"7e461dac-d11c-4e25-94b5-d6976f009157","command":"'/usr/bin/php8.1' 'artisan' cp:sync-data"} 
Your Command output...

原文链接


guanguans
2.4k 声望89 粉丝

No practice, no gain in one's wit.