Logo Oracle Deutschland   Application Express Community
Debugging und Instrumentierung für APEX Anwendungen: APEX_DEBUG

Erscheinungsmonat APEX-Version Datenbankversion
Januar 2014 alle ab 10.2

Den Debug-Modus hat sicherlich jeder APEX-Entwickler schon einmal genutzt. Man schaltet einfach in der Developer Toolbar unten das Debugging mit einem Klick auf Debug ein, lädt die Anwendungsseite neu und schaut sich danach die Meldungen mit Klick auf View Debug an (Abbildungen 1,2 und 3).

APEX bietet dem Entwickler aber noch wesentlich mehr: Das PL/SQL-Paket APEX_DEBUG erlaubt es dem Entwickler, dort eigene Meldungen einzutragen, das Debugging auch ohne Developer Toolbar ein- und auszuschalten und das Aufräumen des Debug-Logs. Lesen Sie in diesem Tipp, wie Sie APEX_DEBUG zur Instrumentierung Ihrer Anwendungen voll ausnutzen und sich die Problemdiagnose massiv vereinfachen können.

Der Debugmodus ist zur Zeit deaktiviert
Abbildung 1: Der Debugmodus ist zur Zeit deaktiviert"

Übersicht über vorhandene Debuginformationen
Abbildung 2: Übersicht über vorhandene Debuginformationen

Ein Klick auf den View Identifier zeigt Debuginformationen im Detail
Abbildung 3: Ein Klick auf den View Identifier zeigt Debuginformationen im Detail

Wie man sieht, gibt APEX eine Menge an Informationen, die bei der Diagnose von Problemen hilfreich sein können. Nicht jedem bekannt ist aber, dass man hier auch selbst Debug-Nachrichten hinzufügen kann - das ist deshalb besonders reizvoll, weil eigene Debug-Informationen dann im Kontext mit den von APEX erstellten Meldungen stehen. Und Sie können APEX_DEBUG überall dort aufrufen, wo Sie PL/SQL-Code einsetzen, also im wesentlichen in PL/SQL-Prozessen, aber auch in Validierungen, Bedingungen und anderen Komponenten. Das sei im folgenden anhand eines einfachen Beispiels gezeigt.

Erzeugen Sie in eine APEX-Seite und darauf einen klassischen Bericht. Als Berichtsquelle hinterlegen Sie eine PL/SQL-Funktion, die eine SQL-Abfrage zurückgibt. Ein Beispiel könnte wie folgt aussehen.

declare
  v_sql varchar2(32767);
begin
  v_sql := 'select ';
  for i in (
    select column_name from user_tab_columns
    where table_name = :P3_TABLE_NAME and data_type = 'VARCHAR2'
  ) loop
    v_sql := v_sql || dbms_assert.enquote_name(i.column_name) || ', ';
  end loop;
  v_sql := substr(v_sql, 1, length(v_sql) - 2);
  v_sql := v_sql || ' from ' || dbms_assert.enquote_name(:P3_TABLE_NAME);
  return v_sql;
end;

Erkennen Sie, was der Bericht darstellen soll? Es geht darum, alle VARCHAR-Spalten der Tabelle, deren Namen im Element P3_TABLE_NAME steckt, auszugeben. Um das ganze testen zu können, braucht Ihre Seite also noch eine Auswahlliste namens P3_TABLE_NAME und eine dynamische Aktion, die den Bericht aktualisiert, wenn diese geändert wurde. Als Werteliste für die Auswahlliste selektieren Sie am besten die Data-Dictionary-View USER_TABLES (Spalte TABLE_NAME). Die APEX-Seite dürfte dann etwa wie in Abbildung 4 aussehen.

Die Testseite in Aktion
Abbildung 4: Die Testseite in Aktion

Ein Problem werden Sie dann bemerken, wenn Sie mal eine Tabelle ohne VARCHAR-Spalten auswählen - dann bekommen Sie nämlich keine Ausgabe. Warum das so ist, werden wir im folgenden mit Hilfe von APEX_DEBUG beantworten. Aber auch ohne konkrete Fehlersuche ist es eine gute Idee, Code zu instrumentieren. Im folgenden sehen Sie den gleichen Code mit zusätzlichen APEX_DEBUG-Aufrufen.

declare
  v_sql varchar2(32767);
begin
  apex_debug.info(
    p_message => 'VARCHAR Report start - chosen table is "%s"',
    p0        => :P3_TABLE_NAME
  );
  v_sql := 'select ';
  for i in (
    select column_name from user_tab_columns
    where table_name = :P3_TABLE_NAME and data_type = 'VARCHAR2'
  ) loop
    apex_debug.info(
      p_message => 'found column: %s',
      p0        => i.column_name
    );
    v_sql := v_sql || dbms_assert.enquote_name(i.column_name) || ', ';
  end loop;
  apex_debug.info(
    p_message => 'SQL before truncating last comma: %s',
    p0        => v_sql
  );
  v_sql := substr(v_sql, 1, length(v_sql) - 2);
  apex_debug.info(
    p_message => 'SQL after truncating last comma: %s',
    p0        => v_sql
  );
  v_sql := v_sql || ' from ' || dbms_assert.enquote_name(:P3_TABLE_NAME);
  apex_debug.info(
    p_message => 'final SQL: %s',
    p0        => v_sql
  );
  apex_debug.info(
    p_message => 'VARCHAR Report end'
  );
  return v_sql;
end;

Führen Sie die Seite nun erneut aus, achten Sie aber darauf, dass der Debug Modus (Developer Toolbar) eingeschaltet ist. Wenn Sie sich die Debug-Meldungen danach ansehen, werden Sie feststellen, dass Ihre eigenen nun dabei sind ...

Debug-Meldungen aus dem eigenen Code betrachten
Abbildung 5: Debug-Meldungen aus dem eigenen Code betrachten

Und nun können Sie auch nachvollziehen, warum es ein Problem ist, wenn die Tabelle gar keine VARCHAR-Spalten hat ...

Mit Debug-Meldungen ein Problem diagnostizieren
Abbildung 6: Mit Debug-Meldungen ein Problem diagnostizieren

... denn wie so oft im Leben, wurde der Fall, dass die Tabelle gar keine VARCHAR-Spalten hat, im Code nicht richtig berücksichtigt; die FOR LOOP läuft in diesem Fall gar nicht durch. Der Fall, dass die gewählte Tabelle keine VARCHAR-Spalten hat, muss also im Code gesondert behandelt werden.

Für manche Debug-Meldungen macht es Sinn, die Werte der APEX-Elemente dazuzuschreiben. Das kann man für ein einzelnes Element natürlich wie in obigem Code machen; für alle Elemente einer Seite wäre das aber sehr mühsam. Daher bietet APEX_DEBUG die Prozedur LOG_PAGE_SESSION_STATE an.

begin
  :
  apex_debug.log_page_session_state(
    p_level => apex_debug.c_log_level_info
  );
  :
end;
Session-State in die Debug-Meldungen aufnehmen
Abbildung 7: Session-State in die Debug-Meldungen aufnehmen

Bereits vorhandene PL/SQL-Pakete und Funktionen verwenden häufig DBMS_OUTPUT zur Ausgabe von Debugging-Nachrichten. Um diese zu den APEX-Debug-Meldungen hinzuzufügen, verwenden Sie die Prozedur LOG_DBMS_OUTPUT. Auch hierzu ein (einfaches) Beispiel: Erzeugen Sie die folgende PL/SQL-Prozedur TEST_LOGGING.

create or replace procedure test_logging as
begin
  dbms_output.enable(20000);
  for i in 1..10 loop
    dbms_output.put_line('Loop '||i);
  end loop;
end;

Erzeugen Sie auf Ihrer APEX-Seite nun einen onLoad-Seitenprozess vom Typ PL/SQL, welcher diese Prozedur schlicht aufruft.

PL/SQL-Prozedur testweise als onLoad-Prozess aufrufen
Abbildung 8: PL/SQL-Prozedur testweise als onLoad-Prozess aufrufen

Natürlich sind die mit DBMS_OUTPUT.PUT_LINE generierten Meldungen zunächst nicht im APEX Debug-Log aufgeführt - das lässt sich aber leicht ändern: Fügen Sie dem PL/SQL-Code Ihres APEX-Seitenprozesses einfach eine Zeile hinzu - wie folgt.

begin
  test_logging;
  apex_debug.log_dbms_output;
end;

Im Debug-Log finden Sie danach diese Meldungen ...

DBMS_OUTPUT-Nachrichten im APEX Debug Log
Abbildung 9: DBMS_OUTPUT-Nachrichten im APEX Debug Log

Sie sind übrigens nicht auf die Developer Toolbar angewiesen, um das Debug Log betrachten zu können - APEX stellt mit APEX_DEBUG_MESSAGES eine Dictionary-View bereit, die sich auch in SQL*Plus oder dem SQL Developer betrachten lässt. Die folgende SQL-Abfrage liest die View aus - Sie erkennen sofort die DBMS_OUTPUT-Nachrichten aus Abbildung 9.

SQL> select message from apex_debug_messages 
  2  where application_id=104 
  3  order by page_view_id asc, message_timestamp asc;

:
Process point: BEFORE_HEADER
Processes - point: BEFORE_HEADER
...Process "Rufe Testprozedur auf" - Type: PLSQL
...Execute Statement: begin begin

MESSAGE
------------------------------------------------------------------------------------
  test_logging;
  apex_debug.log_dbms_output;
end;
end;

Loop 1
Loop 2
Loop 3
Loop 4
Loop 5
Loop 6
Loop 7
Loop 8
Loop 9
:

Das Paket APEX_DEBUG enthält ebenfalls einige Prozeduren zum "Aufräumen" des Debug Logs. So können Sie Meldungen nach Anwendungs-ID (REMOVE_DEBUG_BY_APP), Alter (REMOVE_DEBUG_BY_AGE) oder "View ID" (REMOVE_DEBUG_BY_VIEW) löschen. Das folgende Beispiel zeigt, wie Sie mit SQL*Plus alle Debug-Meldungen für eine Anwendung löschen.

-- APEX-Kontext setzen
SQL>  select workspace_id, workspace from apex_workspaces;

        WORKSPACE_ID WORKSPACE
-------------------- ------------------------------
    3181012624487522 TESTIT

SQL> begin
  2    wwv_flow_api.set_security_group_id(3181012624487522);
  3  end;
  4  /

PL/SQL-Prozedur erfolgreich abgeschlossen.

-- Debug-Log für Anwendung 104 löschen
SQL> begin
  2    apex_debug.remove_debug_by_app(P_APPLICATION_ID => 104);
  3  end;
  4  /

PL/SQL-Prozedur erfolgreich abgeschlossen.

SQL> commit;

Transaktion mit COMMIT abgeschlossen.

-- Probe ...
SQL> select message from apex_debug_messages 
  2  where application_id=104; 

Es wurden keine Zeilen ausgewählt

Bis jetzt haben wir das Debugging stets mit Hilfe der Developer Toolbar aktiviert. Sollen jedoch die Meldungen der Session eines Endbenutzers aufgezeichnet werden, so scheidet dieser Weg aus - denn der Endbenutzer sieht die Developer Toolbar nicht. Allerdings kann das Debugging auch mit einem PL/SQL-Aufruf eingeschaltet werden. Fügen Sie der Anwendungsseite also einen neuen onLoad-Prozess vom Typ PL/SQL hinzu. Er muss als erstes (also bspw. Before Header) ablaufen und diesen PL/SQL-Code ausführen ...

begin
  apex_debug.enable(p_level => apex_debug.c_log_level_info);
end;

Starten Sie die Seite nun, schalten Sie Debugging aber nicht in der Developer Toolbar ein. Dennoch sollten Sie nach dem Seitenaufbau sehen, dass es eingeschaltet wurde - eben vom gerade erzeugten PL/SQL-Prozess. Im Bereich View Debug können Sie sich die Meldungen nun wie gewohnt ansehen. In der Praxis empfiehlt es sich, das Aufzeichnen nicht nur ein- sondern auch wieder auszuschalten. Nehmen Sie dafür einen ebenfalls einen onLoad-Prozess vom Typ PL/SQL und stellen Sie sicher, dass er auf der Seite als letztes ausgeführt wird, also vor Footer oder nach Footer (Before Footer bzw. After Footer). Darin sollte dann der folgenden Code stehen.

begin
  apex_debug.disable;
end;

Wenn Sie diese Prozesse nun noch an eine Bedingung knüpfen, beispielsweise, dass der Endanwender vorher eine Select-Liste Problemdiagnose auf Ja gestellt hat, so kann das Aufzeichnen der Debug-Meldungen für eine Problemsituation nach Bedarf ein- und ausgeschaltet werden; sogar vom Endbenutzer selbst. Als Entwickler hat man danach per SQL*Plus oder SQL Developer bequem Zugriff auf alle Meldungen.

Das Paket APEX_DEBUG sollte eigentlich jedem Entwickler, der hin und wieder eigenen PL/SQL-Code in APEX verwendet (und wer tut das nicht?), geläufig sein. Es erlaubt den programmatischen Umgang mit dem APEX Debug-Log - es kann um eigene Meldungen erweitert oder mit eigenem PL/SQL Code verwaltet werden. Baut man diese Möglichkeiten geschickt in die Anwendungsoberfläche ein, so kann sogar der Endanwender in die Lage versetzt werden, Debug-Meldungen in einer Problemsituation aufzeichnen zu lassen; und zwar ohne Detailkenntnisse des APEX Debuggungs oder der APEX URL-Syntax.

Zurück zur Community-Seite