├── .phpunit-watcher.yml
├── .styleci.yml
├── CHANGELOG.md
├── LICENSE.md
├── README.md
├── composer.json
├── config
├── di.php
└── params.php
├── infection.json.dist
├── psalm-8.3.xml
├── psalm.xml
├── rector.php
└── src
├── FileRotator.php
├── FileRotatorInterface.php
└── FileTarget.php
/.phpunit-watcher.yml:
--------------------------------------------------------------------------------
1 | watch:
2 | directories:
3 | - src
4 | - tests
5 | fileMask: '*.php'
6 | notifications:
7 | passingTests: false
8 | failingTests: false
9 | phpunit:
10 | binaryPath: vendor/bin/phpunit
11 | timeout: 180
12 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: psr12
2 | risky: true
3 |
4 | version: 8.1
5 |
6 | finder:
7 | exclude:
8 | - docs
9 | - vendor
10 |
11 | enabled:
12 | - alpha_ordered_traits
13 | - array_indentation
14 | - array_push
15 | - combine_consecutive_issets
16 | - combine_consecutive_unsets
17 | - combine_nested_dirname
18 | - declare_strict_types
19 | - dir_constant
20 | - fully_qualified_strict_types
21 | - function_to_constant
22 | - hash_to_slash_comment
23 | - is_null
24 | - logical_operators
25 | - magic_constant_casing
26 | - magic_method_casing
27 | - method_separation
28 | - modernize_types_casting
29 | - native_function_casing
30 | - native_function_type_declaration_casing
31 | - no_alias_functions
32 | - no_empty_comment
33 | - no_empty_phpdoc
34 | - no_empty_statement
35 | - no_extra_block_blank_lines
36 | - no_short_bool_cast
37 | - no_superfluous_elseif
38 | - no_unneeded_control_parentheses
39 | - no_unneeded_curly_braces
40 | - no_unneeded_final_method
41 | - no_unset_cast
42 | - no_unused_imports
43 | - no_unused_lambda_imports
44 | - no_useless_else
45 | - no_useless_return
46 | - normalize_index_brace
47 | - php_unit_dedicate_assert
48 | - php_unit_dedicate_assert_internal_type
49 | - php_unit_expectation
50 | - php_unit_mock
51 | - php_unit_mock_short_will_return
52 | - php_unit_namespaced
53 | - php_unit_no_expectation_annotation
54 | - phpdoc_no_empty_return
55 | - phpdoc_no_useless_inheritdoc
56 | - phpdoc_order
57 | - phpdoc_property
58 | - phpdoc_scalar
59 | - phpdoc_singular_inheritdoc
60 | - phpdoc_trim
61 | - phpdoc_trim_consecutive_blank_line_separation
62 | - phpdoc_type_to_var
63 | - phpdoc_types
64 | - phpdoc_types_order
65 | - print_to_echo
66 | - regular_callable_call
67 | - return_assignment
68 | - self_accessor
69 | - self_static_accessor
70 | - set_type_to_cast
71 | - short_array_syntax
72 | - short_list_syntax
73 | - simplified_if_return
74 | - single_quote
75 | - standardize_not_equals
76 | - ternary_to_null_coalescing
77 | - trailing_comma_in_multiline_array
78 | - unalign_double_arrow
79 | - unalign_equals
80 | - empty_loop_body_braces
81 | - integer_literal_case
82 | - union_type_without_spaces
83 |
84 | disabled:
85 | - function_declaration
86 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Yii Logging Library - File Target Change Log
2 |
3 | ## 3.0.1 under development
4 |
5 | - Enh #63: Add 'categories', 'except' and 'exportInterval' setters to default config (@olegbaturin)
6 | - Bug #46: Replace rotate by rename to rotate by copy (@vjik)
7 |
8 | ## 3.0.0 February 17, 2023
9 |
10 | - Chg #53: Adapt configuration group names to Yii conventions (@vjik)
11 |
12 | ## 2.0.1 July 25, 2022
13 |
14 | - Bug #45: Fix definitions config (@rustamwin)
15 |
16 | ## 2.0.0 July 18, 2022
17 |
18 | - Enh #40: Add support for `yiisoft/files` of version `^2.0` (@DplusG)
19 | - Bug #38: Drop `rotateByCopy`, always rotate by rename (@DplusG)
20 | - Bug #43: Add `ext-zlib` to composer requirements (@DplusG)
21 |
22 | ## 1.1.0 May 23, 2022
23 |
24 | - Chg #36: Raise the minimum `yiisoft/log` version to `^2.0` and the minimum PHP version to 8.0 (@rustamwin)
25 |
26 | ## 1.0.4 August 26, 2021
27 |
28 | - Bug #35: Remove `Psr\Log\LoggerInterface` definition from configuration for using multiple targets to application (@devanych)
29 |
30 | ## 1.0.3 April 13, 2021
31 |
32 | - Chg: Adjust config for yiisoft/factory changes (@vjik, @samdark)
33 |
34 | ## 1.0.2 March 23, 2021
35 |
36 | - Chg: Adjust config for new config plugin (@samdark)
37 |
38 | ## 1.0.1 February 22, 2021
39 |
40 | - Chg #29: Replace the default maximum file size for file rotation to `10` megabytes in `params.php` (@devanych)
41 |
42 | ## 1.0.0 February 11, 2021
43 |
44 | Initial release.
45 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2008 by Yii Software ()
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 | * Neither the name of Yii Software nor the names of its
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Yii Logging Library - File Target
6 |
7 |
8 |
9 | [](https://packagist.org/packages/yiisoft/log-target-file)
10 | [](https://packagist.org/packages/yiisoft/log-target-file)
11 | [](https://github.com/yiisoft/log-target-file/actions?query=workflow%3Abuild)
12 | [](https://codecov.io/gh/yiisoft/log-target-file)
13 | [](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/log-target-file/master)
14 | [](https://github.com/yiisoft/log-target-file/actions?query=workflow%3A%22static+analysis%22)
15 | [](https://shepherd.dev/github/yiisoft/log-target-file)
16 |
17 | This package provides the File target for the [yiisoft/log](https://github.com/yiisoft/log). The target:
18 |
19 | - records log messages in a file
20 | - allows you to configure log files rotation
21 | - provides the ability to compress rotated log files
22 |
23 | ## Requirements
24 |
25 | - PHP 8.0 or higher.
26 |
27 | ## Installation
28 |
29 | The package could be installed with [Composer](https://getcomposer.org):
30 |
31 | ```shell
32 | composer require yiisoft/log-target-file
33 | ```
34 |
35 | ## General usage
36 |
37 | Creating a rotator:
38 |
39 | ```php
40 | $rotator = new \Yiisoft\Log\Target\File\FileRotator(
41 | $maxFileSize,
42 | $maxFiles,
43 | $fileMode,
44 | $compressRotatedFiles
45 | );
46 | ```
47 |
48 | - `$maxFileSize (int)` - The maximum file size, in kilo-bytes. Defaults to `10240`, meaning 10MB.
49 | - `$maxFiles (int)` - The number of files used for rotation. Defaults to `5`.
50 | - `$fileMode (int|null)` - The permission to be set for newly created files. Defaults to `null`.
51 | - `$compressRotatedFiles (bool)` - Whether to compress rotated files with gzip. Defaults to `false`.
52 |
53 | Creating a target:
54 |
55 | ```php
56 | $fileTarget = new \Yiisoft\Log\Target\File\FileTarget(
57 | $logFile,
58 | $rotator,
59 | $dirMode,
60 | $fileMode
61 | );
62 | ```
63 |
64 | - `$logFile (string)` - The log file path. Defaults to `/tmp/app.log`.
65 | - `$rotator (\Yiisoft\Log\Target\File\FileRotatorInterface|null)` - Defaults to `null`,
66 | which means that log files will not be rotated.
67 | - `$dirMode (int)` - The permission to be set for newly created directories. Defaults to `0775`.
68 | - `$fileMode (int|null)` - The permission to be set for newly created log files. Defaults to `null`.
69 |
70 | Creating a logger:
71 |
72 | ```php
73 | $logger = new \Yiisoft\Log\Logger([$fileTarget]);
74 | ```
75 |
76 | For use in the [Yii framework](https://www.yiiframework.com/), see the configuration files:
77 | - [`config/di.php`](https://github.com/yiisoft/log-target-file/blob/master/config/di.php)
78 | - [`config/params.php`](https://github.com/yiisoft/log-target-file/blob/master/config/params.php)
79 |
80 | ## Documentation
81 |
82 | For a description of using the logger, see the [yiisoft/log](https://github.com/yiisoft/log) package.
83 |
84 | - [Yii guide to logging](https://github.com/yiisoft/docs/blob/master/guide/en/runtime/logging.md)
85 | - [Internals](docs/internals.md)
86 |
87 | If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that.
88 | You may also check out other [Yii Community Resources](https://www.yiiframework.com/community).
89 |
90 | ## License
91 |
92 | The Yii Logging Library - File Target is free software. It is released under the terms of the BSD License.
93 | Please see [`LICENSE`](./LICENSE.md) for more information.
94 |
95 | Maintained by [Yii Software](https://www.yiiframework.com/).
96 |
97 | ## Support the project
98 |
99 | [](https://opencollective.com/yiisoft)
100 |
101 | ## Follow updates
102 |
103 | [](https://www.yiiframework.com/)
104 | [](https://twitter.com/yiiframework)
105 | [](https://t.me/yii3en)
106 | [](https://www.facebook.com/groups/yiitalk)
107 | [](https://yiiframework.com/go/slack)
108 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yiisoft/log-target-file",
3 | "type": "library",
4 | "description": "Yii Logging Library - File Target",
5 | "keywords": [
6 | "yii",
7 | "framework",
8 | "log",
9 | "logger"
10 | ],
11 | "homepage": "https://www.yiiframework.com/",
12 | "license": "BSD-3-Clause",
13 | "support": {
14 | "issues": "https://github.com/yiisoft/log-target-file/issues",
15 | "source": "https://github.com/yiisoft/log-target-file",
16 | "forum": "https://www.yiiframework.com/forum/",
17 | "wiki": "https://www.yiiframework.com/wiki/",
18 | "irc": "ircs://irc.libera.chat:6697/yii",
19 | "chat": "https://t.me/yii3en"
20 | },
21 | "funding": [
22 | {
23 | "type": "opencollective",
24 | "url": "https://opencollective.com/yiisoft"
25 | },
26 | {
27 | "type": "github",
28 | "url": "https://github.com/sponsors/yiisoft"
29 | }
30 | ],
31 | "require": {
32 | "php": "^8.0",
33 | "ext-zlib": "*",
34 | "yiisoft/files": "^1.0|^2.0",
35 | "yiisoft/log": "^2.0"
36 | },
37 | "require-dev": {
38 | "maglnet/composer-require-checker": "^4.2",
39 | "phpunit/phpunit": "^9.5",
40 | "rector/rector": "^2.0",
41 | "roave/infection-static-analysis-plugin": "^1.25",
42 | "spatie/phpunit-watcher": "^1.23",
43 | "vimeo/psalm": "^4.30|^5.24",
44 | "yiisoft/aliases": "^3.0",
45 | "yiisoft/di": "^1.0"
46 | },
47 | "suggest": {
48 | "ext-zlib": "Enabling gzip compression of rotated files."
49 | },
50 | "autoload": {
51 | "psr-4": {
52 | "Yiisoft\\Log\\Target\\File\\": "src"
53 | }
54 | },
55 | "autoload-dev": {
56 | "psr-4": {
57 | "Yiisoft\\Log\\Target\\File\\Tests\\": "tests"
58 | }
59 | },
60 | "extra": {
61 | "config-plugin-options": {
62 | "source-directory": "config"
63 | },
64 | "config-plugin": {
65 | "di": "di.php",
66 | "params": "params.php"
67 | }
68 | },
69 | "config": {
70 | "sort-packages": true,
71 | "allow-plugins": {
72 | "infection/extension-installer": true,
73 | "composer/package-versions-deprecated": true
74 | }
75 | },
76 | "scripts": {
77 | "test": "phpunit --testdox --no-interaction",
78 | "test-watch": "phpunit-watcher watch"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/config/di.php:
--------------------------------------------------------------------------------
1 | [
14 | 'class' => FileRotator::class,
15 | '__construct()' => [
16 | $params['yiisoft/log-target-file']['fileRotator']['maxFileSize'],
17 | $params['yiisoft/log-target-file']['fileRotator']['maxFiles'],
18 | $params['yiisoft/log-target-file']['fileRotator']['fileMode'],
19 | $params['yiisoft/log-target-file']['fileRotator']['compressRotatedFiles'],
20 | ],
21 | ],
22 |
23 | FileTarget::class => static function (Aliases $aliases, FileRotatorInterface $fileRotator) use ($params) {
24 | $fileTarget = new FileTarget(
25 | $aliases->get($params['yiisoft/log-target-file']['fileTarget']['file']),
26 | $fileRotator,
27 | $params['yiisoft/log-target-file']['fileTarget']['dirMode'],
28 | $params['yiisoft/log-target-file']['fileTarget']['fileMode'],
29 | );
30 |
31 | $fileTarget->setLevels($params['yiisoft/log-target-file']['fileTarget']['levels']);
32 | $fileTarget->setCategories($params['yiisoft/log-target-file']['fileTarget']['categories']);
33 | $fileTarget->setExcept($params['yiisoft/log-target-file']['fileTarget']['except']);
34 | $fileTarget->setExportInterval($params['yiisoft/log-target-file']['fileTarget']['exportInterval']);
35 |
36 | return $fileTarget;
37 | },
38 | ];
39 |
--------------------------------------------------------------------------------
/config/params.php:
--------------------------------------------------------------------------------
1 | [
9 | 'fileTarget' => [
10 | 'file' => '@runtime/logs/app.log',
11 | 'levels' => [
12 | LogLevel::EMERGENCY,
13 | LogLevel::ERROR,
14 | LogLevel::WARNING,
15 | LogLevel::INFO,
16 | LogLevel::DEBUG,
17 | ],
18 | 'categories' => [],
19 | 'except' => [],
20 | 'exportInterval' => 1000,
21 | 'dirMode' => 0755,
22 | 'fileMode' => null,
23 | ],
24 | 'fileRotator' => [
25 | 'maxFileSize' => 10240,
26 | 'maxFiles' => 5,
27 | 'fileMode' => null,
28 | 'compressRotatedFiles' => false,
29 | ],
30 | ],
31 | ];
32 |
--------------------------------------------------------------------------------
/infection.json.dist:
--------------------------------------------------------------------------------
1 | {
2 | "source": {
3 | "directories": [
4 | "src"
5 | ]
6 | },
7 | "logs": {
8 | "text": "php:\/\/stderr",
9 | "stryker": {
10 | "report": "master"
11 | }
12 | },
13 | "mutators": {
14 | "@default": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/psalm-8.3.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/rector.php:
--------------------------------------------------------------------------------
1 | paths([
11 | __DIR__ . '/src',
12 | __DIR__ . '/tests',
13 | ]);
14 |
15 | // register a single rule
16 | $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class);
17 |
18 | // define sets of rules
19 | $rectorConfig->sets([
20 | LevelSetList::UP_TO_PHP_80,
21 | ]);
22 | };
23 |
--------------------------------------------------------------------------------
/src/FileRotator.php:
--------------------------------------------------------------------------------
1 | checkCannotBeLowerThanOne($maxFileSize, '$maxFileSize');
78 | $this->checkCannotBeLowerThanOne($maxFiles, '$maxFiles');
79 |
80 | $this->maxFileSize = $maxFileSize;
81 | $this->maxFiles = $maxFiles;
82 |
83 | if ($compressRotatedFiles && !extension_loaded('zlib')) {
84 | throw new RuntimeException(sprintf(
85 | 'The %s requires the PHP extension "ext-zlib" to compress rotated files.',
86 | self::class,
87 | ));
88 | }
89 |
90 | $this->compressRotatedFiles = $compressRotatedFiles;
91 | }
92 |
93 | public function rotateFile(string $file): void
94 | {
95 | for ($i = $this->maxFiles; $i >= 0; --$i) {
96 | // `$i === 0` is the original file
97 | $rotateFile = $file . ($i === 0 ? '' : '.' . $i);
98 | $newFile = $file . '.' . ($i + 1);
99 |
100 | if ($i === $this->maxFiles) {
101 | $this->safeRemove($this->compressRotatedFiles ? $rotateFile . self::COMPRESS_EXTENSION : $rotateFile);
102 | continue;
103 | }
104 |
105 | if ($this->compressRotatedFiles && is_file($rotateFile . self::COMPRESS_EXTENSION)) {
106 | $this->rotate($rotateFile . self::COMPRESS_EXTENSION, $newFile . self::COMPRESS_EXTENSION);
107 | continue;
108 | }
109 |
110 | if (!is_file($rotateFile)) {
111 | continue;
112 | }
113 |
114 | $this->rotate($rotateFile, $newFile);
115 |
116 | if ($i === 0) {
117 | $this->clear($rotateFile);
118 | }
119 | }
120 | }
121 |
122 | public function shouldRotateFile(string $file): bool
123 | {
124 | return file_exists($file) && @filesize($file) > ($this->maxFileSize * 1024);
125 | }
126 |
127 | /***
128 | * Copy rotated file into new file.
129 | */
130 | private function rotate(string $rotateFile, string $newFile): void
131 | {
132 | copy($rotateFile, $newFile);
133 |
134 | if ($this->compressRotatedFiles && !$this->isCompressed($newFile)) {
135 | $this->compress($newFile);
136 | $newFile .= self::COMPRESS_EXTENSION;
137 | }
138 |
139 | if ($this->fileMode !== null) {
140 | chmod($newFile, $this->fileMode);
141 | }
142 | }
143 |
144 | /**
145 | * Compresses a file with gzip and renames it by appending `.gz` to the file.
146 | */
147 | private function compress(string $file): void
148 | {
149 | $filePointer = FileHelper::openFile($file, 'rb');
150 | flock($filePointer, LOCK_SH);
151 | $gzFile = $file . self::COMPRESS_EXTENSION;
152 | $gzFilePointer = gzopen($gzFile, 'wb9');
153 |
154 | while (!feof($filePointer)) {
155 | gzwrite($gzFilePointer, fread($filePointer, 8192));
156 | }
157 |
158 | flock($filePointer, LOCK_UN);
159 | fclose($filePointer);
160 | gzclose($gzFilePointer);
161 | @unlink($file);
162 | }
163 |
164 | /***
165 | * Clears the file without closing any other process open handles.
166 | *
167 | * @param string $file
168 | */
169 | private function clear(string $file): void
170 | {
171 | $filePointer = FileHelper::openFile($file, 'ab');
172 |
173 | flock($filePointer, LOCK_EX);
174 | ftruncate($filePointer, 0);
175 | flock($filePointer, LOCK_UN);
176 | fclose($filePointer);
177 | }
178 |
179 | /**
180 | * Checks the existence of file and removes it.
181 | */
182 | private function safeRemove(string $file): void
183 | {
184 | if (is_file($file)) {
185 | @unlink($file);
186 | }
187 | }
188 |
189 | /**
190 | * Whether the file is compressed.
191 | */
192 | private function isCompressed(string $file): bool
193 | {
194 | return substr($file, -3, 3) === self::COMPRESS_EXTENSION;
195 | }
196 |
197 | /**
198 | * Checks that the value cannot be lower than one.
199 | *
200 | * @param int $value The value to be checked.
201 | * @param string $argumentName The name of the argument to check.
202 | */
203 | private function checkCannotBeLowerThanOne(int $value, string $argumentName): void
204 | {
205 | if ($value < 1) {
206 | throw new InvalidArgumentException(sprintf(
207 | 'The argument "%s" cannot be lower than 1.',
208 | $argumentName,
209 | ));
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/FileRotatorInterface.php:
--------------------------------------------------------------------------------
1 | logFile);
63 |
64 | if (!file_exists($logPath)) {
65 | FileHelper::ensureDirectory($logPath, $this->dirMode);
66 | }
67 |
68 | $text = $this->formatMessages("\n");
69 | $filePointer = FileHelper::openFile($this->logFile, 'ab');
70 | flock($filePointer, LOCK_EX);
71 |
72 | if ($this->rotator !== null) {
73 | // clear stat cache to ensure getting the real current file size and not a cached one
74 | // this may result in rotating twice when cached file size is used on subsequent calls
75 | clearstatcache();
76 | }
77 |
78 | if ($this->rotator !== null && $this->rotator->shouldRotateFile($this->logFile)) {
79 | flock($filePointer, LOCK_UN);
80 | fclose($filePointer);
81 | $this->rotator->rotateFile($this->logFile);
82 | $writeResult = file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX);
83 | } else {
84 | $writeResult = fwrite($filePointer, $text);
85 | flock($filePointer, LOCK_UN);
86 | fclose($filePointer);
87 | }
88 |
89 | $this->checkWrittenResult($writeResult, $text);
90 |
91 | if ($this->fileMode !== null) {
92 | chmod($this->logFile, $this->fileMode);
93 | }
94 | }
95 |
96 | /**
97 | * Checks the written result.
98 | *
99 | * @param false|int $writeResult The number of bytes written to the file, or FALSE if an error occurs.
100 | * @param string $text The text written to the file.
101 | *
102 | * @throws RuntimeException For unable to export log through file.
103 | */
104 | private function checkWrittenResult(false|int $writeResult, string $text): void
105 | {
106 | if ($writeResult === false) {
107 | throw new RuntimeException(sprintf(
108 | 'Unable to export log through file: %s',
109 | error_get_last()['message'] ?? '',
110 | ));
111 | }
112 |
113 | $textSize = strlen($text);
114 |
115 | if ($writeResult < $textSize) {
116 | throw new RuntimeException(sprintf(
117 | 'Unable to export whole log through file. Wrote %d out of %d bytes.',
118 | $writeResult,
119 | $textSize,
120 | ));
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------