From 176e71132551cee963ed837409d8ed48d31f04c1 Mon Sep 17 00:00:00 2001 From: Simon R Jones Date: Thu, 2 May 2024 12:36:37 +0100 Subject: [PATCH] Add logs tasks, update docs --- docs/README.md | 19 ++- docs/tasks/check-branch.md | 2 +- docs/tasks/display-disk-space.md | 22 ++- docs/tasks/logs.md | 91 +++++++++++++ docs/tasks/show-summary.md | 6 +- recipe/common.php | 5 +- recipe/craftcms.php | 2 +- tasks/check-disk-space.php | 2 +- tasks/log-files.php | 34 ----- tasks/logs.php | 223 +++++++++++++++++++++++++++++++ 10 files changed, 357 insertions(+), 49 deletions(-) create mode 100644 docs/tasks/logs.md delete mode 100644 tasks/log-files.php create mode 100644 tasks/logs.php diff --git a/docs/README.md b/docs/README.md index 477a6db..6bc5beb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,13 +20,22 @@ This package contains [Deployer](https://deployer.org/) recipes used to help dep * Symfony * WordPress -## Tasks +## Deployment tasks * [build-summary](tasks/build-summary.md) - create a `_build_summary.json` file to record deployment info -* [check-branch](tasks/check-branch.md) - ensure only default branch (main/master) is deployed to production -* [check-ssh](tasks/check-ssh.md) - check SSH connection to remote server +* [check:branch](tasks/check-branch.md) - ensure only default branch (main/master) is deployed to production * [confirm-continue](tasks/confirm-continue.md) - ask confirmation from user before continuing with deployment -* [display-disk-space](tasks/display-disk-space.md) - display server disk usage prior to deployment -* [show-summary](tasks/show-summary.md) - display a summary of the current deployment info +* [check:disk-space](tasks/display-disk-space.md) - display server disk usage prior to deployment +* [show](tasks/show-summary.md) - display a summary of the current deployment info * [sync](tasks/sync.md) - sync files or folders from the remote host to local development * [vendors-subpath](tasks/vendors-subpath.md) - Run composer install in a sub-path + +## Utility tasks + +* [check-ssh](tasks/check-ssh.md) - check SSH connection to remote server +* [logs:list] +* [logs:view] +* [logs:search] +* [logs:download] +* [show](tasks/show-summary.md) - display a summary of the current deployment info +* [sync](tasks/sync.md) - sync files or folders from the remote host to local development diff --git a/docs/tasks/check-branch.md b/docs/tasks/check-branch.md index 60368f1..23e6185 100644 --- a/docs/tasks/check-branch.md +++ b/docs/tasks/check-branch.md @@ -15,7 +15,7 @@ No configuration required ## Tasks -- `check-branch` – checks which stage and branch you are trying to deploy to. Ensures that non main branches deployed to production have to be forced. +- `check:branch` – checks which stage and branch you are trying to deploy to. Ensures that non main branches deployed to production have to be forced. ## Usage diff --git a/docs/tasks/display-disk-space.md b/docs/tasks/display-disk-space.md index b651e29..868e215 100644 --- a/docs/tasks/display-disk-space.md +++ b/docs/tasks/display-disk-space.md @@ -6,15 +6,23 @@ Displays the disk usage of the remote server in the terminal Either [install all Studio 24 tasks](../installation.md) or install this individual task by adding to your `deploy.php`: ```php -require 'vendor/studio24/deployer-recipes/tasks/display-disk-space.php'; +require 'vendor/studio24/deployer-recipes/tasks/check-disk-space.php'; ``` ## Configuration -No configuration required +If you set the filesystem mount this check can review disk capacity and warn if it is too low. + +``` +# AWS +set('disk_space_filesystem', '/data'); + +# Mythic Beasts +set('disk_space_filesystem', '/'); +``` ## Tasks -- `display-disk-space` – checks the disk usage of the target server and displays the results +- `check:disk-space` – checks the disk usage of the target server and displays the results ## Usage @@ -27,3 +35,11 @@ Filesystem Size Used Avail Use% Mounted on /dev/nvme0n1p1 8.0G 4.0G 4.1G 50% / /dev/nvme1n1 40G 27G 14G 68% /data ``` + +If you have set `disk_space_filesystem` a warning is displayed if the disk capacity is too low: + +``` +[production] Server disk space is almost full: 94% used +[production] Filesystem Size Used Avail Use% Mounted on +/dev/nvme1n1 40G 38G 2.8G 94% /data +``` diff --git a/docs/tasks/logs.md b/docs/tasks/logs.md new file mode 100644 index 0000000..5e8f89b --- /dev/null +++ b/docs/tasks/logs.md @@ -0,0 +1,91 @@ +# Logs + +Tasks to help you interrogate log files. + +## Tasks + +- [logs:list](#logslist) – List available log files +- [logs:view](#logsview) – View a log file +- [logs:search](#logssearch) - Search a log file +- [logs:download](#logsdownload) - Download a log file + +## Installation + +Either [install all Studio 24 tasks](../installation.md) or install this individual task by adding to your `deploy.php`: + +```php +require 'vendor/studio24/deployer-recipes/tasks/logs.php'; +``` + +## Configuration +Ensure you have set the `log_files` configuration variable in your `deploy.php` file. This can be a string (multiple values separated by a commma) or an array of log files. + +E.g. + +``` +set('log_files', '/data/logs/staging.studio24.net.access.log /data/logs/staging.studio24.net.error.log') +``` + +or: + +``` +set('log_files', [ + '/data/logs/staging.studio24.net.access.log', + '/data/logs/staging.studio24.net.error.log', + 'storage/logs/*.log', +]) +``` + +Please note by using this task you can specify `log_files` as a string or array safely, and it will continue to work with other Deployer log tasks (such as `dep log:app`). + +## Usage + +### logs:list + +List available log files. + +Example: + +``` +dep logs:list production +``` + + +### logs:view + +View a log file, options include: + +* `--logfile=[LOGFILE]` The log file to view - you are asked to choose a file if you don't set this option +* `--lines=[LINES]` Number of lines to display - defaults to 20, 0 returns all lines + +Example: + +``` +dep logs:view production +``` + +### logs:search + +Return matching lines from a log file, options include: + +* `--logfile=[LOGFILE]` The log file to search - you are asked to choose a file if you don't set this option +* `--search=[SEARCH]` Only return lines that match the search string +* `--lines=[LINES]` Number of lines to display - defaults to 20, 0 returns all lines + +Example: + +``` +dep logs:search production +``` + +### logs:download + +Download a logfile to your local computer, options include: + +* `--logfile=[LOGFILE]` The log file to download - you are asked to choose a file if you don't set this option + +Example: + +``` +dep logs:download production +``` diff --git a/docs/tasks/show-summary.md b/docs/tasks/show-summary.md index ea324db..dbbaba0 100644 --- a/docs/tasks/show-summary.md +++ b/docs/tasks/show-summary.md @@ -15,19 +15,19 @@ No configuration is required. ## Tasks -- `show-summary` – retrieves current deployment info and displays in the terminal +- `show` – retrieves current deployment info and displays in the terminal ## Usage Run on any environment to display current deploy information ``` -dep show-summary +dep show ``` eg: ``` -dep show-summary production +dep show production ``` This returns a response detailing what is deployed to production. diff --git a/recipe/common.php b/recipe/common.php index 60c90c3..eef4262 100644 --- a/recipe/common.php +++ b/recipe/common.php @@ -3,6 +3,9 @@ namespace Deployer; // Require all Studio 24 Deployer tasks +use Studio24\DeployerRecipes\Command\LogsSearchCommand; + +// Add custom deployment tasks require_once __DIR__ . '/../tasks/sync.php'; require_once __DIR__ . '/../tasks/build-summary.php'; require_once __DIR__ . '/../tasks/show-summary.php'; @@ -11,7 +14,7 @@ require_once __DIR__ . '/../tasks/confirm-continue.php'; require_once __DIR__ . '/../tasks/vendors-subpath.php'; require_once __DIR__ . '/../tasks/ssh-check.php'; -require_once __DIR__ . '/../tasks/log-files.php'; +require_once __DIR__ . '/../tasks/logs.php'; // Default deployment and HTTP users set('remote_user', 'deploy'); diff --git a/recipe/craftcms.php b/recipe/craftcms.php index def81d6..26b4b0b 100644 --- a/recipe/craftcms.php +++ b/recipe/craftcms.php @@ -43,7 +43,7 @@ desc('Craft storage: reset storage permissions'); task('craft:storage', function () { writeln('Updating storage directory permissions'); - run('deploy:writable'); + invoke('deploy:writable'); }); desc('Runs pending Craft CMS migrations and applies pending project config changes'); diff --git a/tasks/check-disk-space.php b/tasks/check-disk-space.php index bc5e0b6..03b77a4 100644 --- a/tasks/check-disk-space.php +++ b/tasks/check-disk-space.php @@ -13,7 +13,7 @@ desc('Display server disk usage prior to deployment'); task('check:disk-space', function () { - $filesystem = get('', ''); + $filesystem = get('disk_space_filesystem', ''); $threshold = (int) get('disk_space_threshold', 80); if (!empty($filesystem)) { diff --git a/tasks/log-files.php b/tasks/log-files.php deleted file mode 100644 index f0a85e2..0000000 --- a/tasks/log-files.php +++ /dev/null @@ -1,34 +0,0 @@ - 1) { - $logFile = askChoice("Choose a log file to view", $logFiles); - } else { - $logFile = $logFiles[0]; - } - - $search = ask("Enter search term (or leave blank to view last 25 lines and leave log open to view new entries)"); - - if (empty($search)) { - cd('{{current_path}}'); - run(sprintf('tail -n 25 -f %s', $logFile)); - } else { - cd('{{current_path}}'); - run(sprintf('grep --color=always -i %s %s', $search, $logFile)); - } -})->verbose(); diff --git a/tasks/logs.php b/tasks/logs.php new file mode 100644 index 0000000..7a94aa7 --- /dev/null +++ b/tasks/logs.php @@ -0,0 +1,223 @@ +Available log files on %s:', currentHost())); + foreach ($logfiles as $file) { + writeln($file); + } +}); + +desc('View a log file'); +task('logs:view', function () { + + // Get array of log files + if (!has('log_files')) { + warning('Please specify "log_files" option in deploy.php to view log files.'); + return; + } + $logfiles = getLogFilesSettingAsArray(); + $logfiles = expandLogFiles($logfiles); + + // Select log file + $logfile = getLogfileLogsOption($logfiles, 'view'); + + $keepOpen = askConfirmation('Do you want to keep logfile open for new entries (yes/no)?'); + if ($keepOpen) { + // Get terminal screen height + $lines = (int) runLocally('tput lines'); + if ($lines < 10) { + $lines = 20; + } + } else { + // Set number of lines to display + $lines = getLinesLogsOptions(); + } + + // View log file + cd('{{current_path}}'); + if ($lines === 0) { + run(sprintf('less %s', $logfile), real_time_output: true); + } else { + if ($keepOpen) { + run(sprintf('tail -f -n %d %s', $lines, $logfile), real_time_output: true); + return; + } else { + run(sprintf('tail -n %d %s', $lines, $logfile), real_time_output: true); + } + } + + // Build example command + $command = sprintf('dep logs:view %s --logfile=%s --lines=%d', currentHost(), $logfile, $lines); + writeln(sprintf('Run again with: %s', $command)); +}); + +desc('Search a log file'); +task('logs:search', function () { + + // Get array of log files + if (!has('log_files')) { + warning('Please specify "log_files" option in deploy.php to view log files.'); + return; + } + $logfiles = getLogFilesSettingAsArray(); + $logfiles = expandLogFiles($logfiles); + + // Select log file + $logfile = getLogfileLogsOption($logfiles, 'search'); + + // Search terms + if (!empty(input()->getOption('search'))) { + $search = input()->getOption('search'); + } else { + $search = ask("Enter search term"); + while (empty($search)) { + warning('Please specify a search term'); + $search = ask("Enter search term"); + } + } + + // Set number of lines to display + $lines = getLinesLogsOptions(); + + // Search log file + cd('{{current_path}}'); + if ($lines === 0) { + run(sprintf('grep --color=always -i %s %s | less', $search, $logfile), real_time_output: true); + } else { + run(sprintf('grep --color=always -i %s %s | tail -n %d', $search, $logfile, $lines), real_time_output: true); + } + + // Build example command + $command = sprintf('dep logs:search %s --logfile=%s --lines=%d --search="%s"', currentHost(), $logfile, $lines, $search); + writeln(sprintf('Run again with: %s', $command)); +}); + +desc('Download a log file'); +task('logs:download', function () { + + // Get array of log files + if (!has('log_files')) { + warning('Please specify "log_files" option in deploy.php to view log files.'); + return; + } + $logfiles = getLogFilesSettingAsArray(); + $logfiles = expandLogFiles($logfiles); + + // Select log file + $logfile = getLogfileLogsOption($logfiles, 'download'); + + // Destination + $destination = ask('Enter destination path', './'); + + // Download log file + cd('{{current_path}}'); + download($logfile, $destination); + writeln('Log file downloaded to: ' . $destination); + + // Build example command + $command = sprintf('dep logs:download %s --logfile=%s', currentHost(), $logfile); + writeln(sprintf('Run again with: %s', $command)); + +}); + +/** + * Normalise log_files setting so it works with default Deployer tasks (that expect a string) + * @return void + */ +function normaliseLogFilesSetting(): void +{ + if (!has('log_files')) { + return; + } + $logfiles = get('log_files'); + if (is_array($logfiles)) { + set('log_files', implode(' ', $logfiles)); + } +} + +/** + * Return log_files setting as an array so it works with these tasks + * @return void + */ +function getLogFilesSettingAsArray(): array +{ + if (!has('log_files')) { + return []; + } + $logfiles = get('log_files'); + if (!is_array($logfiles)) { + $logfiles = explode(' ', $logfiles); + } + return $logfiles; +} + +/** + * Expand logfiles array to include any wildcard (*) files + * + * @param array $logfiles + * @return array + * @throws Exception\Exception + * @throws Exception\RunException + * @throws Exception\TimeoutException + */ +function expandLogFiles(array $logfiles): array +{ + foreach ($logfiles as $key => $file) { + if (str_contains($file, '*')) { + $path = dirname($file); + $file = basename($file); + cd('{{current_path}}'); + cd($path); + $output = run(sprintf('ls %s', $file)); + $files = explode("\n", $output); + unset($logfiles[$key]); + if (!empty($files)) { + array_walk($files, function (&$value) use ($path) { + $value = $path . DIRECTORY_SEPARATOR . $value; + }); + $logfiles = array_merge($logfiles, $files); + } + } + } + return $logfiles; +} + +function getLogfileLogsOption(array $logfiles, string $action) +{ + if (!empty(input()->getOption('logfile'))) { + return input()->getOption('logfile'); + } elseif (count($logfiles) > 1) { + return askChoice('Choose a log file to ' . $action, $logfiles); + } else { + return $logfiles[0]; + } +} + +function getLinesLogsOptions(): int +{ + if (!empty(input()->getOption('lines'))) { + return (int) input()->getOption('lines'); + } else { + return (int) ask("How many lines to display (0 to view all)", '20'); + } +}