49

I need to debug in pl/sql to figure times of procedures, I want to use:

SELECT systimestamp FROM dual INTO time_db;
DBMS_OUTPUT.PUT_LINE('time before procedure ' || time_db);

but I don't understand where the output goes to and how can I redirect it to a log file that will contain all the data I want to collect?

MatthewMartin
  • 32,326
  • 33
  • 105
  • 164

10 Answers10

38

DBMS_OUTPUT is not the best tool to debug, since most environments don't use it natively. If you want to capture the output of DBMS_OUTPUT however, you would simply use the DBMS_OUTPUT.get_line procedure.

Here is a small example:

SQL> create directory tmp as '/tmp/';

Directory created

SQL> CREATE OR REPLACE PROCEDURE write_log AS
  2     l_line VARCHAR2(255);
  3     l_done NUMBER;
  4     l_file utl_file.file_type;
  5  BEGIN
  6     l_file := utl_file.fopen('TMP', 'foo.log', 'A');
  7     LOOP
  8        EXIT WHEN l_done = 1;
  9        dbms_output.get_line(l_line, l_done);
 10        utl_file.put_line(l_file, l_line);
 11     END LOOP;
 12     utl_file.fflush(l_file);
 13     utl_file.fclose(l_file);
 14  END write_log;
 15  /

Procedure created

SQL> BEGIN
  2     dbms_output.enable(100000);
  3     -- write something to DBMS_OUTPUT
  4     dbms_output.put_line('this is a test');
  5     -- write the content of the buffer to a file
  6     write_log;
  7  END;
  8  /

PL/SQL procedure successfully completed

SQL> host cat /tmp/foo.log

this is a test
Vincent Malgrat
  • 66,725
  • 9
  • 119
  • 171
  • How d'you define variables `l_line VARCHAR2(255); l_done NUMBER; l_file utl_file.file_type;` ? – David Kakauridze Nov 27 '12 at 12:55
  • VARCHAR2, NUMBER are [standard SQL datatypes](http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/datatypes.htm#CHDHAEGF). [`UTL_FILE`](http://docs.oracle.com/cd/E11882_01/appdev.112/e25788/u_file.htm#BABGGEDF) is a standard package. – Vincent Malgrat Nov 27 '12 at 13:01
34

As an alternative to writing to a file, how about writing to a table? Instead of calling DBMS_OUTPUT.PUT_LINE you could call your own DEBUG.OUTPUT procedure something like:

procedure output (p_text varchar2) is
   pragma autonomous_transaction;
begin
   if g_debugging then
      insert into debug_messages (username, datetime, text)
      values (user, sysdate, p_text);
      commit;
   end if;
end;

The use of an autonomous transaction allows you to retain debug messages produced from transactions that get rolled back (e.g. after an exception is raised), as would happen if you were using a file.

The g_debugging boolean variable is a package variable that can be defaulted to false and set to true when debug output is required.

Of course, you need to manage that table so that it doesn't grow forever! One way would be a job that runs nightly/weekly and deletes any debug messages that are "old".

Tony Andrews
  • 129,880
  • 21
  • 220
  • 259
17

use set serveroutput on;

for example:

set serveroutput on;

DECLARE
x NUMBER;
BEGIN
x := 72600;
dbms_output.put_line('The variable X = '); dbms_output.put_line(x);
END;
Mahmoud Hanafy
  • 1,861
  • 3
  • 24
  • 33
16

If you are just testing your PL/SQL in SQL Plus you can direct it to a file like this:

spool output.txt
set serveroutput on

begin
  SELECT systimestamp FROM dual INTO time_db;
  DBMS_OUTPUT.PUT_LINE('time before procedure ' || time_db);
end;
/

spool off

IDEs like Toad and SQL Developer can capture the output in other ways, but I'm not familiar with how.

Stephen Rodriguez
  • 1,037
  • 1
  • 12
  • 22
Tony Andrews
  • 129,880
  • 21
  • 220
  • 259
  • thanks, but I'm testing a different application that uses the pl/sql db and I need the information to be saved to a file while the .sql script is not run from the sqldeveloper, any ideas? –  Sep 21 '09 at 09:36
  • Well, you can obtain the output produced by DBMS_OUTPUT in the calling application by calling DBMS_OUTPUT.ENABLE before writing any messages, and then calling DBMS_OUTPUT.GET_LINE or GET_LINES. But to then put that information into a file would require doing your own file opening/writing/closing, e.g. using UTL_FILE - in which case you may as well use UTL_FILE in the first place! – Tony Andrews Sep 21 '09 at 10:27
5

In addition to Tony's answer, if you are looking to find out where your PL/SQL program is spending it's time, it is also worth checking out this part of the Oracle PL/SQL documentation.

Ian Carpenter
  • 8,346
  • 6
  • 50
  • 82
4

Using UTL_FILE instead of DBMS_OUTPUT will redirect output to a file:

http://oreilly.com/catalog/oraclebip/chapter/ch06.html

Rafa de Castro
  • 2,474
  • 1
  • 24
  • 44
  • thanks for the quick response :) I will try to give this a try (but it sounds really difficult to make it work), do you have any idea how to print the DBMS_OUTPUT to a file (it may be more easy) –  Sep 21 '09 at 09:27
  • The link is dead now, – Fering Feb 10 '20 at 17:57
3

As a side note, remember that all this output is generated in the server side.

Using DBMS_OUTPUT, the text is generated in the server while it executes your query and stored in a buffer. It is then redirected to your client app when the server finishes the query data retrieval. That is, you only get this info when the query ends.

With UTL_FILE all the information logged will be stored in a file in the server. When the execution finishes you will have to navigate to this file to get the information.

Hope this helps.

Juan Calero
  • 4,124
  • 5
  • 31
  • 45
1

Its possible write a file directly to the DB server that hosts your database, and that will change all along with the execution of your PL/SQL program.

This uses the Oracle directory TMP_DIR; you have to declare it, and create the below procedure:

CREATE OR REPLACE PROCEDURE write_log(p_log varchar2)
  -- file mode; thisrequires
--- CREATE OR REPLACE DIRECTORY TMP_DIR as '/directory/where/oracle/can/write/on/DB_server/';
AS
  l_file utl_file.file_type;
BEGIN
  l_file := utl_file.fopen('TMP_DIR', 'my_output.log', 'A');
  utl_file.put_line(l_file, p_log);
  utl_file.fflush(l_file);
  utl_file.fclose(l_file);
END write_log;
/

Here is how to use it:

1) Launch this from your SQL*PLUS client:

BEGIN
  write_log('this is a test');
  for i in 1..100 loop
    DBMS_LOCK.sleep(1);
    write_log('iter=' || i);
  end loop;
  write_log('test complete');
END;
/

2) on the database server, open a shell and

    tail -f -n500 /directory/where/oracle/can/write/on/DB_server/my_output.log
J. Chomel
  • 8,193
  • 15
  • 41
  • 69
-1

An old thread, but there is another alternative.

Since 9i you can use pipelined table function.

First, create a type as a table of varchar:

CREATE TYPE t_string_max IS TABLE OF VARCHAR2(32767);

Second, wrap your code in a pipelined function declaration:

CREATE FUNCTION fn_foo (bar VARCHAR2) -- your params
  RETURN t_string_max PIPELINED IS 
  -- your vars
BEGIN
  -- your code
END;
/

Replace all DBMS_OUTPUT.PUT_LINE for PIPE ROW.

Finally, call it like this:

SELECT * FROM TABLE(fn_foo('param'));

Hope it helps.

Felypp Oliveira
  • 2,147
  • 1
  • 17
  • 17
-4

Try This:

SELECT systimestamp INTO time_db FROM dual ;

DBMS_OUTPUT.PUT_LINE('time before procedure ' || time_db);
demonplus
  • 5,613
  • 12
  • 49
  • 68
Mark
  • 1