mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 04:32:32 +00:00
update skills
This commit is contained in:
@@ -1,176 +0,0 @@
|
||||
# Backend Logs Skill
|
||||
|
||||
Du analysierst Azure Functions Backend-Logs (Application Insights) um Probleme zu debuggen. Du baust KQL-Queries, fuehrst sie aus und korrelierst die Ergebnisse mit dem Source Code.
|
||||
|
||||
## Input
|
||||
|
||||
`$ARGUMENTS` enthaelt entweder:
|
||||
- Eine Beschreibung des Problems (z.B. "warum schlagen die ingest calls fehl")
|
||||
- Einen Funktionsnamen (z.B. "IngestEventsFunction" oder "IngestEvents")
|
||||
- Einen Zeitraum und/oder spezifischen Fehler (z.B. "errors letzte Stunde")
|
||||
- Leer = Health-Overview der letzten Stunde
|
||||
|
||||
## Hilfsskript
|
||||
|
||||
Nutze das Helper-Script `.claude/scripts/query-appinsights.sh` fuer alle Abfragen:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "<KQL-Query>" [timespan]
|
||||
```
|
||||
|
||||
- Timespan-Format: ISO 8601 Duration (z.B. PT1H, PT24H, P7D)
|
||||
- Default-Timespan: PT1H (letzte Stunde)
|
||||
- Das Script nutzt `$AUTOPILOT_MONITOR_APPINSIGHTS_ID` fuer die App Insights App-ID
|
||||
- Ergebnis ist ein JSON-Array mit Objekten (Spaltenname → Wert)
|
||||
|
||||
## Wichtig: Datenmodell
|
||||
|
||||
Die `requests`-Tabelle ist bei diesem Projekt leer (.NET isolated worker Eigenheit). Alle Function-Ausfuehrungsdaten liegen in der `traces`-Tabelle:
|
||||
- **FunctionStarted**: `message` beginnt mit `Executing 'Functions.<Name>'`
|
||||
- **FunctionCompleted**: `message` beginnt mit `Executed 'Functions.<Name>'` und enthaelt `(Succeeded, ...)` oder `(Failed, ...)`
|
||||
- `customDimensions` enthaelt JSON mit `prop__functionName`, `prop__status`, `prop__executionDuration`, `prop__invocationId`
|
||||
- `operation_Name` = Function-Name (ohne "Functions." Prefix)
|
||||
|
||||
## Schritt 1: Problem verstehen und Strategie waehlen
|
||||
|
||||
Analysiere `$ARGUMENTS` und waehle die passende Strategie:
|
||||
|
||||
### Strategie A: Fehler-Analyse (User beschreibt ein Problem)
|
||||
→ Weiter mit Schritt 2a
|
||||
|
||||
### Strategie B: Funktions-Analyse (User nennt eine Function)
|
||||
→ Weiter mit Schritt 2b
|
||||
|
||||
### Strategie C: Allgemeine Uebersicht (Input leer oder User will Status sehen)
|
||||
→ Weiter mit Schritt 2c
|
||||
|
||||
## Schritt 2a: Fehler-Uebersicht
|
||||
|
||||
### Exceptions pruefen:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "exceptions | where timestamp > ago(1h) | summarize count() by outerMessage, problemId | order by count_ desc | take 20"
|
||||
```
|
||||
|
||||
Falls leer, Zeitraum auf 24h erweitern:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "exceptions | where timestamp > ago(24h) | summarize count() by outerMessage, problemId | order by count_ desc | take 20" PT24H
|
||||
```
|
||||
|
||||
### Details zum relevantesten Fehler:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "exceptions | where timestamp > ago(1h) | where outerMessage contains '<fehler_text>' | project timestamp, outerMessage, innermostMessage, details[0].rawStack, operation_Id | order by timestamp desc | take 5"
|
||||
```
|
||||
|
||||
### Fehlgeschlagene Functions:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "traces | where timestamp > ago(1h) | where message startswith 'Executed' | where message contains 'Failed' | project timestamp, message, operation_Name, operation_Id, customDimensions | order by timestamp desc | take 20"
|
||||
```
|
||||
|
||||
### Warnings und Errors in Traces:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "traces | where timestamp > ago(1h) | where severityLevel >= 2 | summarize count() by message, severityLevel | order by count_ desc | take 20"
|
||||
```
|
||||
|
||||
## Schritt 2b: Function-Requests analysieren
|
||||
|
||||
Uebersicht einer Function (Erfolge vs Fehler):
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "traces | where timestamp > ago(1h) | where message startswith 'Executed' | where operation_Name == '<function_name>' | where message contains 'Succeeded' or message contains 'Failed' | extend status = iif(message contains 'Succeeded', 'Succeeded', 'Failed') | extend durationMs = toint(extract('Duration=([0-9]+)ms', 1, message)) | summarize total=count(), succeeded=countif(status == 'Succeeded'), failed=countif(status == 'Failed'), avgMs=avg(durationMs), p95Ms=percentile(durationMs, 95) by operation_Name"
|
||||
```
|
||||
|
||||
Letzte fehlgeschlagene Ausfuehrungen:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "traces | where timestamp > ago(1h) | where message startswith 'Executed' | where operation_Name == '<function_name>' | where message contains 'Failed' | project timestamp, message, operation_Id | order by timestamp desc | take 10"
|
||||
```
|
||||
|
||||
Alle Traces einer Function (letzte Ausfuehrungen):
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "traces | where timestamp > ago(1h) | where operation_Name == '<function_name>' | project timestamp, message, severityLevel, operation_Id | order by timestamp desc | take 50"
|
||||
```
|
||||
|
||||
## Schritt 2c: Health-Overview
|
||||
|
||||
Alle Functions mit Erfolgsrate:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "traces | where timestamp > ago(1h) | where message startswith 'Executed' | where message contains 'Succeeded' or message contains 'Failed' | extend status = iif(message contains 'Succeeded', 'Succeeded', 'Failed') | extend durationMs = toint(extract('Duration=([0-9]+)ms', 1, message)) | summarize total=count(), failed=countif(status == 'Failed'), avgMs=avg(durationMs) by operation_Name | extend failRate=round(100.0 * failed / total, 1) | order by failed desc, total desc | take 30"
|
||||
```
|
||||
|
||||
## Schritt 3: Tiefere Analyse mit Operation-ID
|
||||
|
||||
Wenn du eine operation_Id hast, hole ALLE Telemetrie dazu:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "union traces, exceptions, dependencies | where operation_Id == '<op_id>' | order by timestamp asc | project timestamp, itemType, message, outerMessage, severityLevel, operation_Name, customDimensions" PT24H
|
||||
```
|
||||
|
||||
## Schritt 4: Trace-Logs durchsuchen
|
||||
|
||||
Nach Suchbegriff:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "traces | where timestamp > ago(1h) | where message contains '<suchbegriff>' | project timestamp, message, severityLevel, operation_Name, operation_Id | order by timestamp desc | take 30"
|
||||
```
|
||||
|
||||
Severity-Level: 0=Verbose, 1=Information, 2=Warning, 3=Error, 4=Critical
|
||||
|
||||
## Schritt 5: Dependency-Analyse (externe Aufrufe)
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "dependencies | where timestamp > ago(1h) | where success == false | summarize count() by name, type, target, resultCode | order by count_ desc | take 20"
|
||||
```
|
||||
|
||||
## Schritt 6: Zeitreihen-Analyse (Pattern erkennen)
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-appinsights.sh "traces | where timestamp > ago(24h) | where message startswith 'Executed' | where operation_Name == '<function_name>' | where message contains 'Succeeded' or message contains 'Failed' | extend status = iif(message contains 'Succeeded', 'Succeeded', 'Failed') | summarize total=count(), errors=countif(status == 'Failed') by bin(timestamp, 15m) | order by timestamp asc" PT24H
|
||||
```
|
||||
|
||||
## Schritt 7: Code-Korrelation
|
||||
|
||||
Nachdem du die Fehler identifiziert hast, lies den relevanten Code:
|
||||
|
||||
### Backend Functions (nach Function-Name suchen)
|
||||
- Alle Functions: `src/Backend/AutopilotMonitor.Functions/Functions/`
|
||||
- Unterordner: Admin, Bootstrap, Config, Diagnostics, Feedback, Galactic, Infrastructure, Ingest, Metrics, Progress, Reports, Rules, Sessions
|
||||
- Security: `src/Backend/AutopilotMonitor.Functions/Security/`
|
||||
- Services: `src/Backend/AutopilotMonitor.Functions/Services/`
|
||||
- Middleware: `src/Backend/AutopilotMonitor.Functions/Middleware/`
|
||||
|
||||
### Haeufige Fehlerquellen
|
||||
- **401/403**: `SecurityValidationExtensions.cs`, `AuthenticationMiddleware.cs`, `PolicyEnforcementMiddleware.cs`
|
||||
- **429 Rate Limiting**: `RateLimitService.cs`
|
||||
- **Table Storage**: `TableStorageService*.cs`
|
||||
- **Deserialization**: JSON-Parsing im Function Body
|
||||
- **Tenant-Config**: `TenantConfigurationService.cs`
|
||||
|
||||
## Ausgabe-Format
|
||||
|
||||
1. **Problem-Zusammenfassung**: Was wurde gefunden (1-3 Saetze)
|
||||
2. **Fehler-Details**: Exceptions, fehlgeschlagene Functions mit Kontext
|
||||
3. **Timeline**: Wann das Problem begann/endet, ob es andauert
|
||||
4. **Root-Cause Analyse**: Korrelation mit Code, was den Fehler verursacht
|
||||
5. **Empfehlung**: Was getan werden sollte
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Die Env-Variable `AUTOPILOT_MONITOR_APPINSIGHTS_ID` enthaelt die Application Insights App-ID
|
||||
- `az monitor app-insights query` braucht aktiven Azure CLI Login (`az login`)
|
||||
- Die `requests`-Tabelle ist leer — nutze immer `traces` fuer Function-Ausfuehrungen
|
||||
- Bei grossen Ergebnismengen immer `| take N` verwenden
|
||||
- `operation_Id` verbindet Traces → Exceptions → Dependencies einer einzelnen Ausfuehrung
|
||||
- `operation_Name` = Function-Name (z.B. "IngestEvents", "GetAgentConfig")
|
||||
- `cloud_RoleName` = `autopilotmonitor-api`
|
||||
- Sampling ist aktiv — bei Traces koennen einzelne Eintraege fehlen
|
||||
- Zeitraum-Mapping: PT1H=1h, PT6H=6h, PT24H=24h, P7D=7 Tage
|
||||
- Wenn der User einen Zeitraum nennt (z.B. "letzte 6 Stunden"), passe sowohl den KQL-Filter (`ago(6h)`) als auch den Timespan-Parameter (`PT6H`) an
|
||||
- **KQL-Escaping**: Vermeide `extract()` mit Regex-Backslashes — das Bash→az→KQL Escaping ist fragil. Nutze stattdessen `iif(message contains ..., ...)` oder `has`/`contains` Operatoren
|
||||
- Nutze einfache Anfuehrungszeichen fuer die KQL-Query im Script-Aufruf, damit Bash keine Variablensubstitution macht
|
||||
@@ -1,231 +0,0 @@
|
||||
# Event Query Skill
|
||||
|
||||
Du suchst und analysierst Events aus Azure Table Storage fuer Debugging und Weiterentwicklung. Du kannst Events nach Typ suchen, Kontext um ein bestimmtes Event herum anzeigen, oder einzelne Events abrufen.
|
||||
|
||||
## Input
|
||||
|
||||
`$ARGUMENTS` enthaelt eine natuerlichsprachige Beschreibung was gesucht wird. Beispiele:
|
||||
|
||||
- `"finde app_tracking_summary events mit vielen app namen"` — Suche nach EventType mit Kriterien
|
||||
- `"zeige mir die 5 events vor und nach event abc123... in session def456..."` — Kontext-Fenster
|
||||
- `"event abc123... in session def456..."` — Einzelnes Event abrufen
|
||||
- `"alle error_detected events in session def456..."` — Events nach Typ in einer Session
|
||||
- `"finde phase_transition events"` — Globale Suche ueber alle Events
|
||||
|
||||
## Hilfsskript
|
||||
|
||||
Nutze das Helper-Script `.claude/scripts/query-table.sh` fuer alle Table-Abfragen:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh <TableName> "<OData-Filter>" [select_fields] [top_count]
|
||||
```
|
||||
|
||||
Das Script:
|
||||
- Parst den Connection String aus `$AUTOPILOT_MONITOR_TABLE_CS`
|
||||
- Baut die REST API URL zusammen
|
||||
- Gibt JSON zurueck
|
||||
- `top_count` begrenzt die Ergebnismenge serverseitig (optional)
|
||||
|
||||
## Schritt 1: Intent erkennen
|
||||
|
||||
Analysiere `$ARGUMENTS` und waehle die passende Strategie:
|
||||
|
||||
### Strategie A: Events nach Typ suchen (mit optionalen Kriterien)
|
||||
User sucht Events eines bestimmten EventType, ggf. mit Inhaltskriterien.
|
||||
→ Weiter mit Schritt 2a
|
||||
|
||||
### Strategie B: Kontext-Fenster (Events um ein bestimmtes Event herum)
|
||||
User will Events VOR und NACH einem bestimmten Event sehen.
|
||||
→ Weiter mit Schritt 2b
|
||||
|
||||
### Strategie C: Einzelnes Event abrufen
|
||||
User will ein spezifisches Event mit allen Details sehen.
|
||||
→ Weiter mit Schritt 2c
|
||||
|
||||
## TenantId ermitteln (nur wenn SessionId angegeben)
|
||||
|
||||
Wenn eine SessionId angegeben ist, zuerst TenantId aus der Sessions-Table holen:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Sessions "RowKey eq '${SESSION_ID}'" "PartitionKey,RowKey,Status"
|
||||
```
|
||||
|
||||
- **PartitionKey** = TenantId
|
||||
- Events-PartitionKey wird dann: `{TenantId}_{SessionId}`
|
||||
|
||||
Bei globaler Suche (kein SessionId) ist kein TenantId noetig.
|
||||
|
||||
## Schritt 2a: Events nach Typ suchen
|
||||
|
||||
### Standard: Globale Suche (ueber alle Events)
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "EventType eq '${EVENT_TYPE}'" "" 20
|
||||
```
|
||||
|
||||
Das ist ein Table Scan — in der Preview-Phase kein Problem wegen geringer Datenmenge. `$top=20` als Default-Limit, anpassen wenn User mehr oder weniger will.
|
||||
|
||||
### Optional: Eingeschraenkt auf eine Session
|
||||
|
||||
Wenn User eine SessionId angibt:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "PartitionKey eq '${TENANT_ID}_${SESSION_ID}' and EventType eq '${EVENT_TYPE}'"
|
||||
```
|
||||
|
||||
### Optional: Eingeschraenkt auf einen Tenant
|
||||
|
||||
Alle Events des Typs holen und per `jq` nach TenantId filtern (Table Storage unterstuetzt kein `startswith`):
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "EventType eq '${EVENT_TYPE}'" "" 50 | jq '[.value[] | select(.PartitionKey | startswith("'${TENANT_ID}'_"))]'
|
||||
```
|
||||
|
||||
### Mit Inhaltskriterien (DataJson durchsuchen)
|
||||
|
||||
Wenn User nach Inhalten sucht (z.B. "mit vielen app namen"), erst Events holen, dann DataJson mit `jq` filtern:
|
||||
|
||||
```bash
|
||||
# Beispiel: app_tracking_summary mit vielen Apps
|
||||
.claude/scripts/query-table.sh Events "EventType eq 'app_tracking_summary'" "" 50 | jq '[.value[] | select(.DataJson | fromjson | .apps | length > 5)]'
|
||||
```
|
||||
|
||||
Die `jq`-Filter muessen je nach Suchkriterium angepasst werden. Schau dir zuerst ein paar Beispiel-Events an um die DataJson-Struktur zu verstehen, bevor du filterst.
|
||||
|
||||
### Mit Severity-Filter
|
||||
|
||||
```bash
|
||||
# Nur Warnings und hoeher
|
||||
.claude/scripts/query-table.sh Events "EventType eq '${EVENT_TYPE}' and Severity ge 2" "" 20
|
||||
```
|
||||
|
||||
## Schritt 2b: Kontext-Fenster
|
||||
|
||||
User will N Events vor und nach einem bestimmten Event sehen (Default: 5).
|
||||
|
||||
### 1. Ziel-Event finden
|
||||
|
||||
Per EventId:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "PartitionKey eq '${TENANT_ID}_${SESSION_ID}' and EventId eq '${EVENT_ID}'"
|
||||
```
|
||||
|
||||
Oder per anderer Identifikation (z.B. EventType + ungefaehrer Zeitpunkt). Aus dem Ergebnis den **RowKey** merken.
|
||||
|
||||
### 2. Events davor holen
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "PartitionKey eq '${TENANT_ID}_${SESSION_ID}' and RowKey lt '${TARGET_ROW_KEY}'" "" 200
|
||||
```
|
||||
|
||||
Da Table Storage kein `$orderby` unterstuetzt und RowKeys aufsteigend zurueckkommen, kommen die Events chronologisch. Nutze `jq` um die letzten N zu extrahieren:
|
||||
|
||||
```bash
|
||||
... | jq '[.value | sort_by(.RowKey) | .[-5:]]'
|
||||
```
|
||||
|
||||
### 3. Events danach holen
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "PartitionKey eq '${TENANT_ID}_${SESSION_ID}' and RowKey gt '${TARGET_ROW_KEY}'" "" 5
|
||||
```
|
||||
|
||||
Hier funktioniert `$top=N` direkt, da RowKeys aufsteigend sind.
|
||||
|
||||
### 4. Kombinierte Timeline ausgeben
|
||||
|
||||
Zeige die Events in chronologischer Reihenfolge mit dem Ziel-Event deutlich markiert:
|
||||
|
||||
```
|
||||
... (events davor)
|
||||
>>> TARGET EVENT >>> EventType | Severity | Message
|
||||
... (events danach)
|
||||
```
|
||||
|
||||
## Schritt 2c: Einzelnes Event abrufen
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "PartitionKey eq '${TENANT_ID}_${SESSION_ID}' and EventId eq '${EVENT_ID}'"
|
||||
```
|
||||
|
||||
Wenn kein SessionId bekannt: globale Suche per EventId (Table Scan):
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "EventId eq '${EVENT_ID}'" "" 1
|
||||
```
|
||||
|
||||
Zeige ALLE Felder des Events, inklusive DataJson geparsed und formatiert.
|
||||
|
||||
## Ausgabe-Format
|
||||
|
||||
### Einzelnes Event
|
||||
Alle Properties strukturiert anzeigen. DataJson IMMER parsen und als formatierten JSON-Block darstellen:
|
||||
|
||||
```
|
||||
EventId: abc123...
|
||||
EventType: app_tracking_summary
|
||||
Severity: Info (1)
|
||||
Phase: DeviceSetup (2)
|
||||
Source: Agent
|
||||
Timestamp: 2024-03-15T10:23:45.123Z
|
||||
Message: App tracking summary collected
|
||||
Session: def456...
|
||||
Tenant: ghi789...
|
||||
|
||||
DataJson:
|
||||
{
|
||||
"apps": [...],
|
||||
"totalApps": 12,
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Event-Liste (Suche/Kontext)
|
||||
Kompakte Tabelle mit den wichtigsten Feldern:
|
||||
|
||||
```
|
||||
RowKey (kurz) | EventType | Sev | Phase | Message (gekuerzt)
|
||||
------------------|------------------------|------|--------------|--------------------
|
||||
...0001 | phase_transition | Info | DeviceSetup | Phase changed to...
|
||||
...0002 | app_install_started | Info | DeviceSetup | Installing App X
|
||||
>>> ...0003 | error_detected | Err | DeviceSetup | Failed to install <<<
|
||||
...0004 | app_install_failed | Err | DeviceSetup | App X failed
|
||||
...0005 | phase_transition | Info | AccountSetup | Phase changed to...
|
||||
```
|
||||
|
||||
Bei Interesse an einem bestimmten Event aus der Liste: DataJson auf Nachfrage anzeigen oder direkt wenn es fuer die Frage relevant ist.
|
||||
|
||||
## Bekannte EventTypes
|
||||
|
||||
Referenz der gaengigsten EventTypes:
|
||||
|
||||
**Phase & Lifecycle:** `phase_transition`, `esp_phase_changed`, `enrollment_complete`, `enrollment_failed`, `whiteglove_complete`, `whiteglove_resumed`, `completion_check`
|
||||
|
||||
**App Installation:** `app_install_started`, `app_install_completed`, `app_install_failed`, `app_install_skipped`, `app_download_started`, `download_progress`
|
||||
|
||||
**App Tracking:** `app_tracking_summary`, `app_tracking_started`, `app_tracking_completed`
|
||||
|
||||
**Network:** `network_state_change`, `network_connectivity_check`
|
||||
|
||||
**System:** `error_detected`, `log_entry`, `performance_snapshot`, `gather_result`, `gather_rules_collection_completed`, `agent_metrics_snapshot`
|
||||
|
||||
**ESP:** `esp_state_change`, `esp_ui_state`, `esp_failure`, `esp_provisioning_status`
|
||||
|
||||
**Andere:** `cert_validation`, `desktop_arrived`, `script_completed`, `script_failed`
|
||||
|
||||
## Enum-Mappings
|
||||
|
||||
**Severity:** Trace=-1, Debug=0, Info=1, Warning=2, Error=3, Critical=4
|
||||
|
||||
**Phase:** Unknown=0, DevicePreparation=1, DeviceSetup=2, AccountSetup=3, DeviceESP=4, UserESP=5, Complete=6, PreProvisioning=7
|
||||
|
||||
## Hinweise
|
||||
|
||||
- DataJson ist ein JSON-String als Table-Property — IMMER parsen und anzeigen
|
||||
- `performance_snapshot` und `agent_metrics_snapshot` Events standardmaessig ausblenden (Hochfrequenz-Rauschen), ausser explizit angefragt
|
||||
- RowKey-Format `yyyyMMddHHmmssfff_NNNNNNNNNN` — String-Vergleich funktioniert fuer chronologische Sortierung
|
||||
- EventId ist eine Property, KEIN Key — Suche per EventId ohne PartitionKey ist ein Table Scan
|
||||
- Bei grossen Ergebnismengen `$top` nutzen und User informieren wenn Ergebnisse abgeschnitten wurden
|
||||
- Fuer cross-session Suchen ist Table Scan OK in der Preview — bei Wachstum spaeter einschraenken
|
||||
- Die Env-Variable `AUTOPILOT_MONITOR_TABLE_CS` enthaelt den Connection String mit SAS Token
|
||||
@@ -1,116 +0,0 @@
|
||||
# Session Debug Skill
|
||||
|
||||
Du analysierst eine Autopilot-Monitor Session anhand ihrer SessionId. Du holst dir selbststaendig alle noetigen Daten aus Azure Table Storage via REST API und korrelierst sie mit dem Code.
|
||||
|
||||
## Input
|
||||
|
||||
Der User gibt eine SessionId (GUID) an, optional gefolgt von einer Frage.
|
||||
Beispiel: `/session-debug abc12345-1234-1234-1234-123456789abc warum ist die Session failed?`
|
||||
|
||||
Die gesamte Eingabe wird als `$ARGUMENTS` uebergeben. Die SessionId ist das erste Wort (GUID-Format), alles danach ist die Frage des Users.
|
||||
|
||||
## Hilfsskript
|
||||
|
||||
Nutze das Helper-Script `.claude/scripts/query-table.sh` fuer alle Table-Abfragen:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh <TableName> "<OData-Filter>" [select_fields]
|
||||
```
|
||||
|
||||
Das Script:
|
||||
- Parst den Connection String aus `$AUTOPILOT_MONITOR_TABLE_CS`
|
||||
- Baut die REST API URL zusammen
|
||||
- Gibt JSON zurueck
|
||||
|
||||
## Schritt 1: Session-Metadaten holen
|
||||
|
||||
Zuerst die Session aus der **Sessions** Table holen um die TenantId zu bekommen:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Sessions "RowKey eq '${SESSION_ID}'"
|
||||
```
|
||||
|
||||
Aus dem Ergebnis:
|
||||
- **PartitionKey** = TenantId
|
||||
- **RowKey** = SessionId
|
||||
- Wichtige Felder: `Status`, `CurrentPhase`, `EnrollmentType`, `StartedAt`, `CompletedAt`, `FailureReason`, `EventCount`, `DurationSeconds`, `AgentVersion`, `IsPreProvisioned`, `IsHybridJoin`, `IsUserDriven`, `OsBuild`, `SerialNumber`, `DeviceName`, `Manufacturer`, `Model`
|
||||
|
||||
Fasse die Session-Metadaten kurz zusammen bevor du weiter machst.
|
||||
|
||||
## Schritt 2: Events holen
|
||||
|
||||
Mit TenantId und SessionId die Events aus der **Events** Table holen:
|
||||
|
||||
```bash
|
||||
.claude/scripts/query-table.sh Events "PartitionKey eq '${TENANT_ID}_${SESSION_ID}'"
|
||||
```
|
||||
|
||||
Events-Struktur:
|
||||
- **PartitionKey**: `{TenantId}_{SessionId}`
|
||||
- **RowKey**: `{Timestamp:yyyyMMddHHmmssfff}_{Sequence:D10}` (chronologisch sortiert)
|
||||
- **EventType**: z.B. `phase_transition`, `app_install_started`, `app_install_completed`, `error_detected`, `esp_page_change`, `hello_screen_detected`, `desktop_arrival`, `enrollment_complete`, `enrollment_failed`, `performance_snapshot`, `agent_metrics_snapshot`, `collector_started`, `collector_stopped`, `completion_check`, etc.
|
||||
- **Severity**: Trace=-1, Debug=0, Info=1, Warning=2, Error=3, Critical=4
|
||||
- **Source**: Agent, IME, Registry, WMI, Network
|
||||
- **Phase**: 0=Unknown, 1=DevicePreparation, 2=DeviceSetup, 3=AccountSetup, 4=DeviceESP, 5=UserESP, 6=Complete, 7=PreProvisioning
|
||||
- **Message**: Menschenlesbarer Text
|
||||
- **DataJson**: Strukturierte Metadaten als JSON String (oft die wichtigsten Details!)
|
||||
- **Sequence**: Reihenfolge bei gleichem Timestamp
|
||||
|
||||
## Schritt 3: Bei Bedarf weitere Tables abfragen
|
||||
|
||||
Nur abfragen wenn fuer die Frage relevant:
|
||||
|
||||
### RuleResults (automatische Analyse-Ergebnisse)
|
||||
```bash
|
||||
.claude/scripts/query-table.sh RuleResults "PartitionKey eq '${TENANT_ID}_${SESSION_ID}'"
|
||||
```
|
||||
|
||||
### AppInstallSummaries (App-Installationen)
|
||||
```bash
|
||||
.claude/scripts/query-table.sh AppInstallSummaries "PartitionKey eq '${TENANT_ID}'" "PartitionKey,RowKey,AppName,Status,DurationSeconds,StartedAt,CompletedAt"
|
||||
```
|
||||
Dann filtern: nur Eintraege deren RowKey mit der SessionId beginnt.
|
||||
|
||||
### TenantConfiguration
|
||||
```bash
|
||||
.claude/scripts/query-table.sh TenantConfiguration "PartitionKey eq '${TENANT_ID}' and RowKey eq 'config'"
|
||||
```
|
||||
|
||||
## Schritt 4: Analyse
|
||||
|
||||
Nachdem du die Daten hast:
|
||||
|
||||
1. **Chronologie aufbauen**: Events nach Timestamp sortiert durchgehen
|
||||
2. **Phasen-Uebergaenge**: phase_transition Events zeigen den Ablauf
|
||||
3. **Fehler identifizieren**: Events mit Severity >= 2 (Warning) hervorheben
|
||||
4. **DataJson parsen**: Oft stecken die Details in DataJson - immer reinschauen bei relevanten Events
|
||||
5. **Code-Korrelation**: Wenn der User eine Frage zum Verhalten hat, lies den relevanten Code im Repo und erklaere anhand der Events was passiert ist
|
||||
6. **Completion-Logik**: Bei Fragen zur Session-Completion, schau in:
|
||||
- `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker*.cs`
|
||||
- Die 3 Completion-Pfade: IME pattern, ESP+Hello composite, Desktop+Hello (no-ESP)
|
||||
|
||||
## Wichtige Code-Pfade fuer Event-Analyse
|
||||
|
||||
- **Event-Ingestion**: `src/Backend/AutopilotMonitor.Functions/Functions/IngestEventsFunction.cs`
|
||||
- **Session-Updates**: `src/Backend/AutopilotMonitor.Functions/Services/TableStorageService.Sessions.cs`
|
||||
- **Enrollment Tracker**: `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Tracking/EnrollmentTracker*.cs`
|
||||
- **ESP/Hello Tracker**: `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/EspAndHelloTracker*.cs`
|
||||
- **Desktop Arrival**: `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/DesktopArrivalDetector.cs`
|
||||
- **Collectors**: `src/Agent/AutopilotMonitor.Agent.Core/Monitoring/Collectors/`
|
||||
- **Web Session View**: `src/Web/autopilot-monitor-web/app/sessions/[sessionId]/`
|
||||
- **Shared Models/Enums**: `src/Shared/AutopilotMonitor.Shared/`
|
||||
|
||||
## Ausgabe-Format
|
||||
|
||||
1. **Session-Uebersicht**: Status, Typ, Dauer, Geraet (kompakt, tabellarisch)
|
||||
2. **Event-Timeline**: Wichtigste Events chronologisch mit Timestamp, EventType, Message (nicht alle - nur relevante, keine performance_snapshot/agent_metrics_snapshot)
|
||||
3. **Auffaelligkeiten**: Errors, Warnings, ungewoehnliche Muster
|
||||
4. **Antwort auf User-Frage**: Falls der User eine spezifische Frage hat, diese gezielt beantworten mit Verweis auf Events UND Code
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Die Env-Variable `AUTOPILOT_MONITOR_TABLE_CS` enthaelt den Connection String mit SAS Token
|
||||
- Bei vielen Events (>100) kann die Ausgabe gross werden - fokussiere auf relevante Events, filtere performance_snapshot und agent_metrics_snapshot raus wenn sie nicht relevant sind
|
||||
- DataJson Felder enthalten oft die wichtigsten Details - IMMER reinschauen bei relevanten Events
|
||||
- Wenn ein Event unklar ist, schau im Code nach wo dieser EventType erzeugt wird (grep nach dem EventType-String)
|
||||
- Phase-Enum: Unknown=0, DevicePreparation=1, DeviceSetup=2, AccountSetup=3, DeviceESP=4, UserESP=5, Complete=6, PreProvisioning=7
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Helper script to query Application Insights via Azure CLI
|
||||
# Usage: query-appinsights.sh "<KQL-Query>" [timespan]
|
||||
#
|
||||
# Requires AUTOPILOT_MONITOR_APPINSIGHTS_ID environment variable (App Insights App-ID GUID)
|
||||
# Timespan format: ISO 8601 Duration (e.g., PT1H, PT24H, P7D). Default: PT1H
|
||||
|
||||
QUERY="$1"
|
||||
TIMESPAN="${2:-PT1H}"
|
||||
|
||||
if [ -z "$QUERY" ]; then
|
||||
echo "Usage: query-appinsights.sh \"<KQL-Query>\" [timespan]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$AUTOPILOT_MONITOR_APPINSIGHTS_ID" ]; then
|
||||
echo "ERROR: AUTOPILOT_MONITOR_APPINSIGHTS_ID environment variable not set"
|
||||
echo "Set it to the Application Insights App ID (Azure Portal > API Access > Application ID)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure az CLI is in PATH (Windows/Git Bash may not have it)
|
||||
if ! command -v az &>/dev/null; then
|
||||
for AZ_DIR in \
|
||||
"/c/Program Files/Microsoft SDKs/Azure/CLI2/wbin" \
|
||||
"/c/Program Files (x86)/Microsoft SDKs/Azure/CLI2/wbin"; do
|
||||
if [ -d "$AZ_DIR" ]; then
|
||||
export PATH="$AZ_DIR:$PATH"
|
||||
break
|
||||
fi
|
||||
done
|
||||
if ! command -v az &>/dev/null; then
|
||||
echo "ERROR: Azure CLI (az) not found. Install it or add it to PATH."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Execute query
|
||||
RESULT=$(az monitor app-insights query \
|
||||
--app "$AUTOPILOT_MONITOR_APPINSIGHTS_ID" \
|
||||
--analytics-query "$QUERY" \
|
||||
--offset "$TIMESPAN" \
|
||||
2>&1)
|
||||
|
||||
EXIT_CODE=$?
|
||||
if [ $EXIT_CODE -ne 0 ]; then
|
||||
echo "ERROR: az query failed (exit code $EXIT_CODE)"
|
||||
echo "$RESULT"
|
||||
exit $EXIT_CODE
|
||||
fi
|
||||
|
||||
# Transform nested table format (columns+rows) into readable JSON array of objects
|
||||
python3 -c "
|
||||
import json, sys
|
||||
|
||||
data = json.loads(sys.stdin.read())
|
||||
tables = data.get('tables', data.get('Tables', []))
|
||||
if not tables:
|
||||
print('[]')
|
||||
sys.exit(0)
|
||||
|
||||
table = tables[0]
|
||||
cols = [c['name'] for c in table['columns']]
|
||||
rows = table['rows']
|
||||
|
||||
result = []
|
||||
for row in rows:
|
||||
obj = {}
|
||||
for i, col in enumerate(cols):
|
||||
val = row[i] if i < len(row) else None
|
||||
if val is not None:
|
||||
obj[col] = val
|
||||
result.append(obj)
|
||||
|
||||
print(json.dumps(result, indent=2, default=str))
|
||||
" <<< "$RESULT"
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Helper script to query Azure Table Storage via REST API
|
||||
# Usage: query-table.sh <table_name> <odata_filter> [select_fields] [top_count]
|
||||
#
|
||||
# Requires AUTOPILOT_MONITOR_TABLE_CS environment variable with format:
|
||||
# TableEndpoint=https://....table.core.windows.net/;SharedAccessSignature=sv=...
|
||||
|
||||
TABLE_NAME="$1"
|
||||
FILTER="$2"
|
||||
SELECT="$3"
|
||||
|
||||
if [ -z "$TABLE_NAME" ] || [ -z "$FILTER" ]; then
|
||||
echo "Usage: query-table.sh <table_name> <odata_filter> [select_fields]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$AUTOPILOT_MONITOR_TABLE_CS" ]; then
|
||||
echo "ERROR: AUTOPILOT_MONITOR_TABLE_CS environment variable not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Parse connection string
|
||||
ENDPOINT=$(echo "$AUTOPILOT_MONITOR_TABLE_CS" | sed -n 's/.*TableEndpoint=\([^;]*\).*/\1/p' | sed 's:/*$::')
|
||||
SAS=$(echo "$AUTOPILOT_MONITOR_TABLE_CS" | sed -n 's/.*SharedAccessSignature=\(.*\)/\1/p')
|
||||
|
||||
if [ -z "$ENDPOINT" ] || [ -z "$SAS" ]; then
|
||||
echo "ERROR: Could not parse connection string (need TableEndpoint and SharedAccessSignature)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# URL-encode the OData filter for use in query string
|
||||
ENCODED_FILTER=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$FILTER', safe=''))" 2>/dev/null)
|
||||
if [ -z "$ENCODED_FILTER" ]; then
|
||||
# Fallback: manual encoding of common OData chars
|
||||
ENCODED_FILTER=$(echo "$FILTER" | sed "s/ /%20/g; s/'/%27/g")
|
||||
fi
|
||||
|
||||
# Build full URL with query params
|
||||
QUERY_URL="${ENDPOINT}/${TABLE_NAME}()?${SAS}&\$filter=${ENCODED_FILTER}"
|
||||
if [ -n "$SELECT" ]; then
|
||||
ENCODED_SELECT=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$SELECT', safe=''))" 2>/dev/null)
|
||||
if [ -z "$ENCODED_SELECT" ]; then
|
||||
ENCODED_SELECT="$SELECT"
|
||||
fi
|
||||
QUERY_URL="${QUERY_URL}&\$select=${ENCODED_SELECT}"
|
||||
fi
|
||||
TOP="$4"
|
||||
if [ -n "$TOP" ]; then
|
||||
QUERY_URL="${QUERY_URL}&\$top=${TOP}"
|
||||
fi
|
||||
|
||||
# Execute query
|
||||
curl -s -H "Accept: application/json;odata=nometadata" \
|
||||
-H "x-ms-version: 2024-11-04" \
|
||||
--globoff \
|
||||
"$QUERY_URL"
|
||||
Reference in New Issue
Block a user