1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Core;
6:
7: use Psr\Log\LogLevel;
8:
9: trait DebugTrait
10: {
11: /** @var bool Is debug enabled? */
12: public $debug = false;
13:
14: /** @var array<string, array<int, string>> Helps debugTraceChange. */
15: protected array $_previousTrace = [];
16:
17: /**
18: * Outputs message to STDERR.
19: */
20: protected function _echoStderr(string $message): void
21: {
22: file_put_contents('php://stderr', $message, \FILE_APPEND);
23: }
24:
25: /**
26: * Logs with an arbitrary level.
27: *
28: * @param mixed $level
29: * @param string|\Stringable $message
30: * @param array<mixed> $context
31: */
32: public function log($level, $message, array $context = []): void
33: {
34: if (TraitUtil::hasAppScopeTrait($this) && $this->issetApp() && $this->getApp()->logger instanceof \Psr\Log\LoggerInterface) {
35: $this->getApp()->logger->log($level, $message, $context);
36: } else {
37: $this->_echoStderr($message . "\n");
38: }
39: }
40:
41: /**
42: * Detailed debug information.
43: *
44: * @param bool|string|\Stringable $message
45: * @param array<mixed> $context
46: */
47: public function debug($message, array $context = []): void
48: {
49: // using this to switch on/off the debug for this object
50: if (is_bool($message)) {
51: $this->debug = $message;
52:
53: return;
54: }
55:
56: // if debug is enabled, then log it
57: if ($this->debug) {
58: if (!TraitUtil::hasAppScopeTrait($this) || !$this->issetApp() || !$this->getApp()->logger instanceof \Psr\Log\LoggerInterface) {
59: $message = '[' . static::class . ']: ' . $message;
60: }
61: $this->log(LogLevel::DEBUG, $message, $context);
62: }
63: }
64:
65: /**
66: * Method designed to intercept one of the hardest-to-debug situations within Agile Toolkit.
67: *
68: * Suppose you define a hook and the hook needs to be called only once, but somehow it is
69: * being called multiple times. You want to know where and how those calls come through.
70: *
71: * Place debugTraceChange inside your hook and give unique $trace identifier. If the method
72: * is invoked through different call paths, this debug info will be logged.
73: *
74: * Do not use this method in production code !!!
75: */
76: public function debugTraceChange(string $trace = 'default'): void
77: {
78: $bt = [];
79: foreach (debug_backtrace() as $frame) {
80: if (isset($frame['file'])) {
81: $bt[] = $frame['file'] . ':' . $frame['line'];
82: }
83: }
84:
85: if (isset($this->_previousTrace[$trace]) && array_diff($this->_previousTrace[$trace], $bt)) {
86: $d1 = array_diff($this->_previousTrace[$trace], $bt);
87: $d2 = array_diff($bt, $this->_previousTrace[$trace]);
88:
89: $this->log(LogLevel::DEBUG, 'Call path for ' . $trace . ' has diverged (was ' . implode(', ', $d1) . ', now ' . implode(', ', $d2) . ")\n");
90: }
91:
92: $this->_previousTrace[$trace] = $bt;
93: }
94:
95: /**
96: * System is unusable.
97: *
98: * @param string|\Stringable $message
99: * @param array<mixed> $context
100: */
101: public function emergency($message, array $context = []): void
102: {
103: $this->log(LogLevel::EMERGENCY, $message, $context);
104: }
105:
106: /**
107: * Action must be taken immediately.
108: *
109: * Example: Entire website down, database unavailable, etc. This should
110: * trigger the SMS alerts and wake you up.
111: *
112: * @param string|\Stringable $message
113: * @param array<mixed> $context
114: */
115: public function alert($message, array $context = []): void
116: {
117: $this->log(LogLevel::ALERT, $message, $context);
118: }
119:
120: /**
121: * Critical conditions.
122: *
123: * Example: Application component unavailable, unexpected exception.
124: *
125: * @param string|\Stringable $message
126: * @param array<mixed> $context
127: */
128: public function critical($message, array $context = []): void
129: {
130: $this->log(LogLevel::CRITICAL, $message, $context);
131: }
132:
133: /**
134: * Runtime errors that do not require immediate action but should typically
135: * be logged and monitored.
136: *
137: * @param string|\Stringable $message
138: * @param array<mixed> $context
139: */
140: public function error($message, array $context = []): void
141: {
142: $this->log(LogLevel::ERROR, $message, $context);
143: }
144:
145: /**
146: * Exceptional occurrences that are not errors.
147: *
148: * Example: Use of deprecated APIs, poor use of an API, undesirable things
149: * that are not necessarily wrong.
150: *
151: * @param string|\Stringable $message
152: * @param array<mixed> $context
153: */
154: public function warning($message, array $context = []): void
155: {
156: $this->log(LogLevel::WARNING, $message, $context);
157: }
158:
159: /**
160: * Normal but significant events.
161: *
162: * @param string|\Stringable $message
163: * @param array<mixed> $context
164: */
165: public function notice($message, array $context = []): void
166: {
167: $this->log(LogLevel::NOTICE, $message, $context);
168: }
169:
170: /**
171: * Interesting events.
172: *
173: * Example: User logs in, SQL logs.
174: *
175: * @param string|\Stringable $message
176: * @param array<mixed> $context
177: */
178: public function info($message, array $context = []): void
179: {
180: $this->log(LogLevel::INFO, $message, $context);
181: }
182: }
183: