1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: namespace SmartFactory;
12:
13: use \SmartFactory\Interfaces\IDebugProfiler;
14:
15: 16: 17: 18: 19:
20: class DebugProfiler implements IDebugProfiler
21: {
22: 23: 24: 25: 26: 27: 28:
29: protected ?string $log_path = null;
30:
31: 32: 33: 34: 35: 36: 37: 38:
39: protected bool $write_source_file_and_line_by_debug = false;
40:
41: 42: 43: 44: 45: 46: 47: 48: 49: 50:
51: private static int $profile_time;
52:
53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73:
74: public function init(array $parameters): void
75: {
76: $this->write_source_file_and_line_by_debug = !empty($parameters["write_source_file_and_line_by_debug"]);
77:
78: if (empty($parameters["log_path"])) {
79: throw new \Exception("Log path is not specified!");
80: }
81:
82: $this->log_path = rtrim(str_replace("\\", "/", $parameters["log_path"]), "/") . "/";
83:
84: if (!file_exists($this->log_path) || !is_writable($this->log_path)) {
85: throw new \Exception(sprintf("The log path '%s' is not writable!", $this->log_path));
86: }
87: }
88:
89: 90: 91: 92: 93: 94: 95: 96: 97: 98:
99: public function enableFileAndLineDetails(bool $state): void
100: {
101: $this->write_source_file_and_line_by_debug = $state;
102: }
103:
104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114:
115: protected function extract_call_stack(array $btrace): string
116: {
117: if (empty($btrace)) {
118: return "";
119: }
120:
121: $trace = "";
122:
123: $indent = "";
124: foreach ($btrace as $btrace_entry) {
125:
126: if (!empty($btrace_entry["function"]) && ($btrace_entry["function"] == "handle_error" || str_contains($btrace_entry["function"], "{closure}") || $btrace_entry["function"] == "handleError" || $btrace_entry["function"] == "trigger_error")) {
127: continue;
128: }
129:
130: if (empty($btrace_entry["file"])) {
131: continue;
132: }
133:
134: if (!empty($btrace_entry["function"])) {
135: $function = $btrace_entry["function"];
136:
137: if (preg_match("/{closure:([^}{]+)}/", $function, $matches)) {
138: $trace .= $indent . "{closure:" . $this->trim_path(str_replace("\\", "/", $matches[1])) . "}" . "(";
139: } else {
140: $trace .= $indent . str_replace("SmartFactory\\", "", $function) . "(";
141: }
142:
143: $trace .= ")";
144: }
145:
146: $trace .= " [";
147:
148: $trace .= $this->trim_path(str_replace("\\", "/", $btrace_entry["file"]));
149:
150: $trace .= ", ";
151:
152: if (empty($btrace_entry["line"])) {
153: $trace .= "line number undefined";
154: } else {
155: $trace .= $btrace_entry["line"];
156: }
157:
158: $trace .= "]";
159:
160: $trace .= "\r\n";
161:
162: $indent .= " ";
163: }
164:
165: return trim($trace);
166: }
167:
168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188:
189: public function debugMessage(string $message, bool $write_call_stack = false, string $file_name = "debug.log"): void
190: {
191: $logfile = $this->log_path . $file_name;
192:
193: $backfiles = debug_backtrace();
194:
195: if (basename($backfiles[0]['file']) == "short_functions_inc.php" && $backfiles[0]['function'] == "debugMessage") {
196: $file = empty($backfiles[1]['file']) ? "" : $backfiles[1]['file'];
197: $line = empty($backfiles[1]['line']) ? "" : $backfiles[1]['line'];
198: } else {
199: $file = empty($backfiles[0]['file']) ? "" : $backfiles[0]['file'];
200: $line = empty($backfiles[0]['line']) ? "" : $backfiles[0]['line'];
201: }
202:
203: $prefix = "";
204:
205: if ($this->write_source_file_and_line_by_debug && !empty($file) && !empty($line)) {
206: $prefix = "#URI: " . ($_SERVER["REQUEST_URI"] ?? "") . "\r\n" .
207: "#Source: " . $this->trim_path(str_replace("\\", "/", $file)) . ", " . $line;
208: }
209:
210: if ($write_call_stack) {
211: $prefix .= "\r\n#Callstack:\r\n" . $this->extract_call_stack($backfiles);
212: }
213:
214: $prefix .= "\r\n\r\n";
215:
216: $message = $prefix . trim($message);
217:
218: $message .= "\r\n-------\r\n";
219:
220: if ((!file_exists($logfile) && is_writable($this->log_path)) || is_writable($logfile)) {
221: if (file_put_contents($logfile, $message, FILE_APPEND) === false) {
222: throw new \Exception(sprintf("Error by writing the log file '%s'!", $logfile));
223: }
224: } else {
225: throw new \Exception(sprintf("The log file '%s' is not writable!", $logfile));
226: }
227: }
228:
229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246:
247: public function startProfilePoint(string $message): void
248: {
249: $this->debugMessage($message, false,"profile.log");
250:
251: self::$profile_time = microtime(true);
252: }
253:
254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272:
273: public function fixProfilePoint(string $message): void
274: {
275: if (!empty(self::$profile_time)) {
276: $message = $message . ": " . number_format(microtime(true) - self::$profile_time, 3, ".", "") . " seconds";
277: }
278:
279: $this->debugMessage($message, false, "profile.log");
280:
281: self::$profile_time = microtime(true);
282: }
283:
284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295:
296: public function clearLogFile(string $file_name): void
297: {
298: $file = $this->log_path . $file_name;
299:
300: if ((!file_exists($file) && is_writable($this->log_path)) || is_writable($file)) {
301: @unlink($file);
302: }
303: }
304:
305: 306: 307: 308: 309: 310: 311: 312: 313:
314: public function clearLogFiles(): void
315: {
316: $dir = $this->log_path;
317: $files = scandir($dir);
318: foreach ($files as $file) {
319: if ($file == "." || $file == ".." || is_dir($dir . $file)) {
320: continue;
321: }
322:
323: $pi = pathinfo($dir . $file);
324:
325: if (empty($pi["extension"]) || strtolower($pi["extension"]) != "log") {
326: continue;
327: }
328:
329: $this->clearLogFile($file);
330: }
331: }
332:
333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343:
344: protected function trim_path(string $path): string
345: {
346: $common_prefix = common_prefix(str_replace("\\", "/", __DIR__), $path);
347:
348: return str_replace($common_prefix, "", $path);
349: }
350: }