lwazi 1.9.1 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -8,9 +8,10 @@ use Lwazi\Core\Services\LwaziLogger;
|
|
|
8
8
|
class LogsCommand extends Command
|
|
9
9
|
{
|
|
10
10
|
protected $signature = 'lwazi:logs
|
|
11
|
-
{action=show : Action to perform (show, clear, trace)}
|
|
11
|
+
{action=show : Action to perform (show, clear, trace, tail, watch)}
|
|
12
12
|
{trace_id? : Trace ID to show details}
|
|
13
|
-
{--limit=20 : Number of recent traces to show}
|
|
13
|
+
{--limit=20 : Number of recent traces to show}
|
|
14
|
+
{--interval=2 : Refresh interval in seconds for tail/watch}';
|
|
14
15
|
|
|
15
16
|
protected $description = 'View and manage Lwazi trace logs for debugging';
|
|
16
17
|
|
|
@@ -24,6 +25,8 @@ class LogsCommand extends Command
|
|
|
24
25
|
'trace' => $this->showTrace($this->argument('trace_id')),
|
|
25
26
|
'clear' => $this->clearLogs(),
|
|
26
27
|
'info' => $this->showLogInfo(),
|
|
28
|
+
'tail' => $this->tailLogs(),
|
|
29
|
+
'watch' => $this->tailLogs(),
|
|
27
30
|
default => $this->showHelp(),
|
|
28
31
|
};
|
|
29
32
|
}
|
|
@@ -132,6 +135,10 @@ class LogsCommand extends Command
|
|
|
132
135
|
$this->line(" <fg=yellow>php artisan lwazi:logs trace <id></> Show trace details");
|
|
133
136
|
$this->line(" <fg=yellow>php artisan lwazi:logs clear</> Clear all logs");
|
|
134
137
|
$this->line(" <fg=yellow>php artisan lwazi:logs info</> Show this info");
|
|
138
|
+
$this->line("");
|
|
139
|
+
$this->line('Real-time SSE endpoints:');
|
|
140
|
+
$this->line(" <fg=cyan>GET /lwazi/logs/stream</> Live stream all traces");
|
|
141
|
+
$this->line(" <fg=cyan>GET /lwazi/logs/stream/{id}</> Live stream specific trace");
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
protected function showHelp(): void
|
|
@@ -146,8 +153,54 @@ class LogsCommand extends Command
|
|
|
146
153
|
$this->line(" <fg=yellow>php artisan lwazi:logs trace <id></> Show trace details");
|
|
147
154
|
$this->line(" <fg=yellow>php artisan lwazi:logs clear</> Clear all logs");
|
|
148
155
|
$this->line(" <fg=yellow>php artisan lwazi:logs info</> Show log info");
|
|
156
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs tail</> Live tail new traces");
|
|
157
|
+
$this->line(" <fg=yellow>php artisan lwazi:logs tail --interval=1</> Faster refresh");
|
|
149
158
|
$this->line("");
|
|
150
159
|
$this->line('The trace logs show the flow of a message through the Lwazi system,');
|
|
151
160
|
$this->line('including intent detection, data fetching, and response generation.');
|
|
152
161
|
}
|
|
162
|
+
|
|
163
|
+
protected function tailLogs(): void
|
|
164
|
+
{
|
|
165
|
+
$interval = (int) $this->option('interval');
|
|
166
|
+
$lastCount = 0;
|
|
167
|
+
$lastTraceId = null;
|
|
168
|
+
|
|
169
|
+
$this->info("Live tailing Lwazi traces (Ctrl+C to stop)");
|
|
170
|
+
$this->info("Refresh interval: {$interval}s");
|
|
171
|
+
$this->line(str_repeat('=', 80));
|
|
172
|
+
|
|
173
|
+
while (true) {
|
|
174
|
+
$traces = LwaziLogger::getRecentTraces(10);
|
|
175
|
+
$currentCount = count($traces);
|
|
176
|
+
|
|
177
|
+
if ($currentCount !== $lastCount || ($traces && $traces[0]['id'] ?? null) !== $lastTraceId) {
|
|
178
|
+
$this->newLine();
|
|
179
|
+
$this->info("[" . date('H:i:s') . "] New activity detected ({$currentCount} traces)");
|
|
180
|
+
|
|
181
|
+
foreach ($traces as $trace) {
|
|
182
|
+
$id = $trace['id'] ?? 'unknown';
|
|
183
|
+
$message = substr($trace['message'] ?? 'no message', 0, 50);
|
|
184
|
+
$duration = round($trace['duration_ms'] ?? 0, 2);
|
|
185
|
+
$spans = count($trace['spans'] ?? []);
|
|
186
|
+
$endTime = $trace['end_human'] ?? 'running';
|
|
187
|
+
|
|
188
|
+
$this->line(" <fg=yellow>ID:</> {$id}");
|
|
189
|
+
$this->line(" <fg=cyan>Message:</> {$message}");
|
|
190
|
+
$this->line(" <fg=cyan>Duration:</> {$duration}ms | Spans: {$spans} | Ended: {$endTime}");
|
|
191
|
+
|
|
192
|
+
if (!empty($trace['response'])) {
|
|
193
|
+
$response = substr($trace['response'], 0, 80);
|
|
194
|
+
$this->line(" <fg=green>Response:</> {$response}...");
|
|
195
|
+
}
|
|
196
|
+
$this->line("");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
$lastCount = $currentCount;
|
|
200
|
+
$lastTraceId = $traces[0]['id'] ?? null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
sleep($interval);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
153
206
|
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<?php
|
|
2
|
+
|
|
3
|
+
namespace Lwazi\Core\Http\Controllers;
|
|
4
|
+
|
|
5
|
+
use Illuminate\Routing\Controller;
|
|
6
|
+
use Illuminate\Http\Request;
|
|
7
|
+
use Lwazi\Core\Services\LwaziLogger;
|
|
8
|
+
use Illuminate\Support\Facades\Response;
|
|
9
|
+
|
|
10
|
+
class LogsStreamController extends Controller
|
|
11
|
+
{
|
|
12
|
+
protected array $seenTraceIds = [];
|
|
13
|
+
|
|
14
|
+
public function stream(Request $request): \Symfony\Component\HttpFoundation\StreamedResponse
|
|
15
|
+
{
|
|
16
|
+
$lastCount = 0;
|
|
17
|
+
|
|
18
|
+
return Response::stream(function () {
|
|
19
|
+
$seenTraceIds = [];
|
|
20
|
+
$lastCount = 0;
|
|
21
|
+
|
|
22
|
+
while (true) {
|
|
23
|
+
$traces = LwaziLogger::getRecentTraces(50);
|
|
24
|
+
$currentCount = count($traces);
|
|
25
|
+
|
|
26
|
+
$newTraces = array_filter($traces, function($trace) use (&$seenTraceIds) {
|
|
27
|
+
$id = $trace['id'] ?? null;
|
|
28
|
+
if ($id && !in_array($id, $seenTraceIds)) {
|
|
29
|
+
$seenTraceIds[] = $id;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
$updatedTraces = [];
|
|
36
|
+
foreach ($traces as $trace) {
|
|
37
|
+
$id = $trace['id'] ?? null;
|
|
38
|
+
if ($id && isset($trace['end_time'])) {
|
|
39
|
+
$key = "updated_{$id}";
|
|
40
|
+
if (!in_array($key, $seenTraceIds)) {
|
|
41
|
+
$seenTraceIds[] = $key;
|
|
42
|
+
$updatedTraces[] = $trace;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!empty($newTraces) || !empty($updatedTraces) || $currentCount !== $lastCount) {
|
|
48
|
+
$payload = [
|
|
49
|
+
'type' => 'update',
|
|
50
|
+
'timestamp' => date('Y-m-d H:i:s'),
|
|
51
|
+
'traces' => array_values(array_merge(
|
|
52
|
+
array_map(fn($t) => array_merge($t, ['_event' => 'new']), $newTraces),
|
|
53
|
+
array_map(fn($t) => array_merge($t, ['_event' => 'completed']), $updatedTraces)
|
|
54
|
+
)),
|
|
55
|
+
'stats' => [
|
|
56
|
+
'total' => $currentCount,
|
|
57
|
+
],
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
echo "data: " . json_encode($payload) . "\n\n";
|
|
61
|
+
ob_flush();
|
|
62
|
+
flush();
|
|
63
|
+
|
|
64
|
+
$lastCount = $currentCount;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
echo ": heartbeat\n\n";
|
|
68
|
+
ob_flush();
|
|
69
|
+
flush();
|
|
70
|
+
|
|
71
|
+
sleep(2);
|
|
72
|
+
}
|
|
73
|
+
}, 200, [
|
|
74
|
+
'Content-Type' => 'text/event-stream',
|
|
75
|
+
'Cache-Control' => 'no-cache',
|
|
76
|
+
'Connection' => 'keep-alive',
|
|
77
|
+
'X-Accel-Buffering' => 'no',
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public function trace(string $traceId): \Symfony\Component\HttpFoundation\StreamedResponse
|
|
82
|
+
{
|
|
83
|
+
return Response::stream(function () use ($traceId) {
|
|
84
|
+
$lastData = null;
|
|
85
|
+
|
|
86
|
+
while (true) {
|
|
87
|
+
$trace = LwaziLogger::getTrace($traceId);
|
|
88
|
+
|
|
89
|
+
if ($trace && $trace !== $lastData) {
|
|
90
|
+
echo "data: " . json_encode([
|
|
91
|
+
'type' => 'trace_update',
|
|
92
|
+
'trace' => $trace,
|
|
93
|
+
]) . "\n\n";
|
|
94
|
+
ob_flush();
|
|
95
|
+
flush();
|
|
96
|
+
$lastData = $trace;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if ($trace && isset($trace['end_time'])) {
|
|
100
|
+
echo "data: " . json_encode([
|
|
101
|
+
'type' => 'trace_complete',
|
|
102
|
+
'trace' => $trace,
|
|
103
|
+
]) . "\n\n";
|
|
104
|
+
ob_flush();
|
|
105
|
+
flush();
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
echo ": heartbeat\n\n";
|
|
110
|
+
ob_flush();
|
|
111
|
+
flush();
|
|
112
|
+
|
|
113
|
+
sleep(1);
|
|
114
|
+
}
|
|
115
|
+
}, 200, [
|
|
116
|
+
'Content-Type' => 'text/event-stream',
|
|
117
|
+
'Cache-Control' => 'no-cache',
|
|
118
|
+
'Connection' => 'keep-alive',
|
|
119
|
+
'X-Accel-Buffering' => 'no',
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -6,6 +6,7 @@ use Illuminate\Support\ServiceProvider;
|
|
|
6
6
|
use Illuminate\Support\Facades\Route;
|
|
7
7
|
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
|
|
8
8
|
use Lwazi\Core\Services\LwaziService;
|
|
9
|
+
use Lwazi\Core\Services\LwaziLogger;
|
|
9
10
|
use Lwazi\Core\Tools\ToolRegistry;
|
|
10
11
|
use Lwazi\Core\Rag\RagService;
|
|
11
12
|
use Lwazi\Core\Services\KnowledgeBaseGenerator;
|
|
@@ -46,11 +47,15 @@ class LwaziServiceProvider extends ServiceProvider
|
|
|
46
47
|
|
|
47
48
|
public function boot(): void
|
|
48
49
|
{
|
|
50
|
+
new LwaziLogger();
|
|
51
|
+
|
|
49
52
|
$this->loadViewsFrom(__DIR__ . '/../../resources/views', 'lwazi');
|
|
50
53
|
|
|
51
54
|
Route::prefix(config('lwazi.route_prefix', 'lwazi'))->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class])->group(function () {
|
|
52
55
|
Route::post('/chat', \Lwazi\Core\Http\Controllers\ChatController::class . '@chat');
|
|
53
56
|
Route::get('/status', \Lwazi\Core\Http\Controllers\ChatController::class . '@status');
|
|
57
|
+
Route::get('/logs/stream', \Lwazi\Core\Http\Controllers\LogsStreamController::class . '@stream');
|
|
58
|
+
Route::get('/logs/stream/{traceId}', \Lwazi\Core\Http\Controllers\LogsStreamController::class . '@trace');
|
|
54
59
|
});
|
|
55
60
|
|
|
56
61
|
$this->publishes(
|
|
@@ -10,14 +10,13 @@ class LwaziLogger
|
|
|
10
10
|
protected static ?string $currentTraceId = null;
|
|
11
11
|
protected static array $currentSpans = [];
|
|
12
12
|
protected static array $spansByTrace = [];
|
|
13
|
-
protected static string $logPath;
|
|
13
|
+
protected static ?string $logPath = null;
|
|
14
14
|
protected static bool $enabled = true;
|
|
15
15
|
protected static int $maxStoredTraces = 100;
|
|
16
16
|
|
|
17
17
|
public function __construct()
|
|
18
18
|
{
|
|
19
|
-
self
|
|
20
|
-
self::ensureLogDirectory();
|
|
19
|
+
self::initLogPath();
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
public static function setEnabled(bool $enabled): void
|
|
@@ -30,8 +29,16 @@ class LwaziLogger
|
|
|
30
29
|
return self::$enabled;
|
|
31
30
|
}
|
|
32
31
|
|
|
32
|
+
protected static function initLogPath(): void
|
|
33
|
+
{
|
|
34
|
+
if (self::$logPath === null) {
|
|
35
|
+
self::$logPath = storage_path('lwazi/logs');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
protected static function ensureLogDirectory(): void
|
|
34
40
|
{
|
|
41
|
+
self::initLogPath();
|
|
35
42
|
if (!is_dir(self::$logPath)) {
|
|
36
43
|
mkdir(self::$logPath, 0755, true);
|
|
37
44
|
}
|
|
@@ -597,6 +604,7 @@ class LwaziLogger
|
|
|
597
604
|
|
|
598
605
|
public static function getLogPath(): string
|
|
599
606
|
{
|
|
600
|
-
|
|
607
|
+
self::initLogPath();
|
|
608
|
+
return self::$logPath ?? '';
|
|
601
609
|
}
|
|
602
610
|
}
|