How to make Harbour issue automatically a run-time error log in a text file such as hb_out.log?

1,639 views
Skip to first unread message

Jun

unread,
Aug 18, 2013, 6:55:27 AM8/18/13
to harbou...@googlegroups.com
Hi all,

I noticed that when there is a run-time internal error in my application, Harbour won't produce an error log in a text file just like the one produced by Xbase++.known as "error.log." I googled for hours, but haven't came across the same problem in the forums.

I learned about set(_SET_HBOUTLOG,"whatever.log"), but I couldn't make it work. I tried the following suggestion:   

PROCEDURE Main()
   LOCAL n
   ? "We are running and now an error will raise"
   n++      // an error should raise here
   HB_SYMBOL_UNUSED( n )
   RETURN

True, indeed, an error was raised. After all, it was one of Harbour developers who wrote it. However, it printed an error on the screen only, no so-called "hb_out.log" or some text file containing the error information displayed on the screen.

Somewhere, Viktor Szakáts mentioned about "ErrorInHandler()." So I tried the following experiment:

PROC Main()
    set(_SET_HBOUTLOG,"jun.log")
    ErrorInHandler() // to force an internal error
    RETURN

The one I needed came out at last, an error log printed in a text file; in my experiment I called it as "jun.log". Here's the text info in the text file that ErrorInHandler() created.

Application Internal Error - d:\dev\13\10MenuH\5_1_0\10MenuLb\try.exe
Terminated at: 2013.08.16 16:24:49
Info: JUN.LOG
Unrecoverable error 9001: Error recovery failure
Called from ERRORINHANDLER(0)
Called from MAIN(6) in try.prg
------------------------------------------------------------------------

However, when I applied the ErrorInHandler() function in another error-scenario, Harbour becomes silent again. Here's what I did using my first example: I inserted ErrorInHandler(). Observe the following:

PROCEDURE Main()
   LOCAL n
   ? "We are running and now an error will raise"
   n++      // an error should raise here
   ErrorInHandler()  // <<-----------------
   HB_SYMBOL_UNUSED( n )
   RETURN

My worse experience was that when I compiled my application with gtwvt (hybrid gui) it won't give an error message even the one Harbour is displaying on screen when there's a run-time error on pure text mode app. I know that I have this trouble because I don't know Harbour as much as others do.

Is there any one out there who can help me out of this trouble?

Thank you so much.

JUN


Kevin Carmody

unread,
Aug 18, 2013, 7:58:35 AM8/18/13
to harbou...@googlegroups.com
On 8/18/2013 5:55 AM, Jun wrote:
Hi all,

I noticed that when there is a run-time internal error in my application, Harbour won't produce an error log in a text file just like the one produced by Xbase++.known as "error.log." I googled for hours, but haven't came across the same problem in the forums.

I learned about set(_SET_HBOUTLOG,"whatever.log"), but I couldn't make it work. I tried the following suggestion:   

PROCEDURE Main()
   LOCAL n
   ? "We are running and now an error will raise"
   n++      // an error should raise here
   HB_SYMBOL_UNUSED( n )
   RETURN

True, indeed, an error was raised. After all, it was one of Harbour developers who wrote it. However, it printed an error on the screen only, no so-called "hb_out.log" or some text file containing the error information displayed on the screen.

This is expected behavior.  Harbour raises an error here because an uninitialized local variable has a value of NIL, and n++ attempts a numeric operation on a NIL variable, which is an error.  The default error handler, which you are using since you did not declare an alternative error handler, displays an onscreen error message in an alert box with several choices.  This error handler does not have an option for printing to a log.



Somewhere, Viktor Szakáts mentioned about "ErrorInHandler()." So I tried the following experiment:

PROC Main()
    set(_SET_HBOUTLOG,"jun.log")
    ErrorInHandler() // to force an internal error
    RETURN

The one I needed came out at last, an error log printed in a text file; in my experiment I called it as "jun.log". Here's the text info in the text file that ErrorInHandler() created.

Application Internal Error - d:\dev\13\10MenuH\5_1_0\10MenuLb\try.exe
Terminated at: 2013.08.16 16:24:49
Info: JUN.LOG
Unrecoverable error 9001: Error recovery failure
Called from ERRORINHANDLER(0)
Called from MAIN(6) in try.prg

This is also expected behavior.  An internal error is a condition which forces the program to exit unconditionally.  An internal error does not call call the error handler, because an error handler may allow you to continue execution, an execution cannot continue after an internal error.  The error in your first example is not an internal error, but the error that ErrorInHandler() raises is an internal error.  SET_HBOUTLOG apparently allows you to capture error information for internal errors only. 

If you want to log errors for non-internal errors, you should define an alternative error handler that writes to a log file.  The default error handler is in the Harbour source code in src\rtl\errsys.prg.  You can modify this so that it generates an error log.  Use ERRORBLOCK() to set the error handler.  There are several examples of this in the tests folder.


------------------------------------------------------------------------

However, when I applied the ErrorInHandler() function in another error-scenario, Harbour becomes silent again. Here's what I did using my first example: I inserted ErrorInHandler(). Observe the following:

PROCEDURE Main()
   LOCAL n
   ? "We are running and now an error will raise"
   n++      // an error should raise here
   ErrorInHandler()  // <<-----------------
   HB_SYMBOL_UNUSED( n )
   RETURN


This will generate a normal error on the n++ line.  If you continue execution, it will generate an internal error on the ErrorInHander() line and then exit.  It will not generate an error log since you did not request one.


My worse experience was that when I compiled my application with gtwvt (hybrid gui) it won't give an error message even the one Harbour is displaying on screen when there's a run-time error on pure text mode app. I know that I have this trouble because I don't know Harbour as much as others do.

gtwvt is a GUI driver, and for GUI apps you should have a GUI error handler.  The default error handler is console.



Is there any one out there who can help me out of this trouble?

Thank you so much.

JUN



Kevin

elch

unread,
Aug 18, 2013, 1:26:40 PM8/18/13
to harbou...@googlegroups.com

Hi Jun,


look here for an easy example. Sure, you can add more info like date and time etc..:

https://groups.google.com/d/msg/harbour-users/pQx9rIp8i90/aJ4iP9-WdwwJ


The new errorblock can be set f.e. at start in main function.

You can also handle different errorblocks for different occasions, like expected run time errors in a sub-routine, and then restore it back to the one before ...


Basic background info:

http://x-hacker.org/ng/53guide/ng40105.html


regards

Rolf

Klas Engwall

unread,
Aug 18, 2013, 4:39:45 PM8/18/13
to harbou...@googlegroups.com
Hi Jun,

> I noticed that when there is a run-time internal error in my
> application, Harbour won't produce an error log in a text file just like
> the one produced by Xbase++.known as "error.log." I googled for hours,
> but haven't came across the same problem in the forums.

I don't know what an error log from Xbase++ looks like, but if you want
a really big log that includes every setting you can think of, then you
can borrow the errorblock from the xhb contrib. You might want to remove
an "x" here and there in the headings and elsewhere :-)

Here is the source: contrib\xhb\xhberr.prg

Regards,
Klas

Davaoz Best

unread,
Aug 19, 2013, 1:13:48 AM8/19/13
to harbou...@googlegroups.com
Hi,

Thank you all, Kevin, elch, and Klas for your prompt reply. I haven't thoroughly investigated the following program yet, which I copied from your suggested sources, yet it seemed my problem as to how to make Harbour print a text file when there's a run-time internal error is considered resolved.

I don't know however, if you have thoughts on it as regards its refinement. There are important variables from the source-program, which I dropped on purpose, some retained without purpose at all--the result of copy-paste, of course.

Here's the program which is printing a text file when there is a run-time internal error, which I called "Error.Log,"

//--------------------------------------------------------------------//
#include "error.ch"
#include "fileio.ch"

PROC MAIN
local oldErr
oldErr:=errorblock()
errorblock({|e| myerror( e, oldErr ) } )
cls
begin sequence
use xx new
end sequence
errorblock(oldErr)
return

FUNCTION MyError( e, bHandler ) // from the forum as suggested.
   Local xRes
   // Local cMsg:=""
   // Local i
   // cMsg+= e:description +" - "
   // cMsg+= e:operation +" - "
   // if ( !Empty(e:osCode) )
   //  cMsg += " (DOS Error " + alltrim(str(e:osCode)) +;
   //               ") " +CRLF
   // end
   //
   // i := 2
   // while ( !Empty(ProcName(i)) )
   //  cMsg+= "Called from:"+ Pad(Trim(ProcName(i)),15) + ;
   //           "(" + str(ProcLine(i),6) + ")  " +CRLF
   //         i++
   // end
   // memowrit("error.log",cMsg)

   LogError(e) // a replacement from xhberr.prg
   xRes:=Eval(bHandler,e)
RETURN (xres)

//---------------------------------------------------------------------
STATIC FUNCTION LogError( oerr ) // adopted from contrib/xhb/xhberr.prg

   LOCAL cLogFile    := "Error.Log"
   LOCAL lAppendLog  := .F. // create a new error log (default) .T. = append to a existing one.
   LOCAL nCols
   LOCAL nCount
   LOCAL nHandle
   LOCAL nBytes
   LOCAL nHandle2   := F_ERROR
   LOCAL cLogFile2  := "_error.log"
   LOCAL cBuff      := ""
   LOCAL nRead

   nCols := MaxCol()
   // Alert( "An error occured, Information will be ;written to error.log" )
   IF ! lAppendLog
      nHandle := FCreate( cLogFile, FC_NORMAL )
   ELSE
      IF ! hb_FileExists( cLogFile )
         nHandle := FCreate( cLogFile, FC_NORMAL )
      ELSE
         nHandle  := FCreate( cLogFile2, FC_NORMAL )
         nHandle2 := FOpen( cLogFile, FO_READ )
      ENDIF
   ENDIF
   IF nHandle < 3 .AND. !( Lower( cLogFile ) == "Error.Log" )
      // Force creating error.log in case supplied log file cannot
      // be created for any reason
      cLogFile := "Error.Log"
      nHandle := FCreate( cLogFile, FC_NORMAL )
   ENDIF
   IF nHandle < 3
   ELSE
      FWriteLine( nHandle, PadR( "&&=== ERROR.LOG: Harbour Error Log ", 79, "=" ) )
      FWriteLine( nHandle, "" )

      FWriteLine( nHandle, "Date...............: " + DToC( Date() )  )
      FWriteLine( nHandle, "Time...............: " + Time()          )

      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, "Application name...: " + hb_CmdArgArgV() )
      FWriteLine( nHandle, "Workstation name...: " + NetName() )
      FWriteLine( nHandle, "Available memory...: " + strvalue( Memory( 0 ) )  )
      FWriteLine( nHandle, "Current disk.......: " + DiskName() )
      FWriteLine( nHandle, "Current directory..: " + CurDir() )
      FWriteLine( nHandle, "Free disk space....: " + strvalue( DiskSpace() ) )
      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, "Operating system...: " + OS() )
      FWriteLine( nHandle, "xHarbour version...: " + Version() )
      FWriteLine( nHandle, "xHarbour built on..: " + hb_BuildDate() )
      FWriteLine( nHandle, "C/C++ compiler.....: " + hb_Compiler() )

      FWriteLine( nHandle, "Multi Threading....: " + iif( hb_mtvm(), "YES", "NO" ) )
      FWriteLine( nHandle, "VM Optimization....: " + strvalue( hb_VMMode() ) )

      IF hb_IsFunction( "Select" )
         FWriteLine( nHandle, "" )
         FWriteLine( nHandle, "Current Area ......:" + strvalue( Eval( hb_macroBlock( "Select()" ) ) ) )
      ENDIF

      FWriteLine( nHandle, "" )

      IF nCols > 0
         FWriteLine( nHandle, PadR( "&&=== WORK AREA: Detailed Work Area Items ", nCols, "=" ) )
      ELSE
         FWriteLine( nHandle, "Detailed Work Area Items " )
      ENDIF
      FWriteLine( nHandle, "" )

      hb_WAEval( {||
         IF hb_IsFunction( "Select" )
            FWriteLine( nHandle, "Work Area No ......: " + strvalue( Do( "Select" ) ) )
         ENDIF
         IF hb_IsFunction( "Alias" )
            FWriteLine( nHandle, "Alias .............: " + Do( "Alias" ) )
         ENDIF
         IF hb_IsFunction( "RecNo" )
            FWriteLine( nHandle, "Current Recno .....: " + strvalue( Do( "RecNo" ) ) )
         ENDIF
         IF hb_IsFunction( "dbFilter" )
            FWriteLine( nHandle, "Current Filter ....: " + Do( "dbFilter" ) )
         ENDIF
         IF hb_IsFunction( "dbRelation" )
            FWriteLine( nHandle, "Relation Exp. .....: " + Do( "dbRelation" ) )
         ENDIF
         IF hb_IsFunction( "IndexOrd" )
            FWriteLine( nHandle, "Index Order .......: " + strvalue( Do( "IndexOrd" ) ) )
         ENDIF
         IF hb_IsFunction( "IndexKey" )
            FWriteLine( nHandle, "Active Key ........: " + strvalue( Eval( hb_macroBlock( "IndexKey( 0 )" ) ) ) )
         ENDIF
         FWriteLine( nHandle, "" )
         RETURN .T.
         } )

      FWriteLine( nHandle, "" )
      IF nCols > 0
         FWriteLine( nHandle, PadR( "&&=== INFO: Internal Error Handling Information  ", nCols, "=" ) )
      ELSE
         FWriteLine( nHandle, " Internal Error Handling Information  " )
      ENDIF
      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, "Subsystem Call ....: " + oErr:subsystem() )
      FWriteLine( nHandle, "System Code .......: " + strvalue( oErr:suBcode() ) )
      FWriteLine( nHandle, "Default Status ....: " + strvalue( oerr:candefault() ) )
      FWriteLine( nHandle, "Description .......: " + oErr:description() )
      FWriteLine( nHandle, "Operation .........: " + oErr:operation() )
      FWriteLine( nHandle, "Arguments .........: " + Arguments( oErr ) )
      FWriteLine( nHandle, "Involved File .....: " + oErr:filename() )
      FWriteLine( nHandle, "Dos Error Code ....: " + strvalue( oErr:oscode() ) )

#ifdef __XHARBOUR__
#ifdef HB_THREAD_SUPPORT
      FWriteLine( nHandle, "Running threads ...: " + strvalue( oErr:RunningThreads() ) )
      FWriteLine( nHandle, "VM thread ID ......: " + strvalue( oErr:VmThreadId() ) )
      FWriteLine( nHandle, "OS thread ID ......: " + strvalue( oErr:OsThreadId() ) )
#endif
#endif

      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, PadR( "&&=== TRACE THROUGH ", nCols, "=" ) )
      FWriteLine( nHandle, PadR( ProcName(), 18 ) + " : Line# " + Transform( ProcLine(), "999,999" ) + " in Module: " + ProcFile() )
      nCount := 3
      WHILE ! Empty( ProcName( ++nCount ) )           // 21
         FWriteLine( nHandle, PadR( ProcName( nCount ), 18 ) + " : Line# " + Transform( ProcLine( nCount ), "999,999" ) + " in Module: " + ProcFile( nCount ) )
      ENDDO
      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, "" )
#if 0
      /* NOTE: Adapted from hb_mvSave() source in Harbour RTL. [vszakats] */
      LOCAL nScope, nCount, tmp, cName, xValue

      FWriteLine( nHandle, PadC( " Available Memory Variables ", nCols, "+" ) )
      FWriteLine( nHandle, "" )

      FOR EACH nScope IN { HB_MV_PUBLIC, HB_MV_PRIVATE }
         nCount := __mvDbgInfo( nScope )
         FOR tmp := 1 TO nCount
            xValue := __mvDbgInfo( nScope, tmp, @cName )
            IF ValType( xValue ) $ "CNDTL"
               FWriteLine( nHandle, "      " + cName + " TYPE " + ValType( xValue ) + " " + hb_CStr( xValue ) )
            ENDIF
         NEXT
      NEXT
#endif
      IF lAppendLog .AND. nHandle2 != F_ERROR
         nBytes := FSeek( nHandle2, 0, FS_END )
         cBuff := Space( 10 )
         FSeek( nHandle2, 0, FS_SET )
         WHILE nBytes > 0
            nRead := FRead( nHandle2, @cBuff, hb_BLen( cBuff ) )
            FWrite( nHandle, cBuff, nRead )
            nBytes -= nRead
            cBuff := Space( 10 )
         ENDDO
         FClose( nHandle2 )
         FClose( nHandle )
         FErase( cLogFile )
         FRename( cLogFile2, cLogFile )
      ELSE
         FClose( nHandle )
      ENDIF
   ENDIF
   RETURN .F.

STATIC FUNCTION strvalue( c, l )
   LOCAL cr := ""
   __defaultNIL( @l, .F. )
   SWITCH ValType( c )
   CASE "C"
      cr := c
      EXIT
   CASE "N"
      cr := hb_ntos( c )
      EXIT
   CASE "M"
      cr := c
      EXIT
   CASE "D"
      cr := DToC( c )
      EXIT
   CASE "L"
      cr := iif( l, iif( c, "On", "Off" ), iif( c, ".T.", ".F." ) )
      EXIT
   ENDSWITCH

   RETURN Upper( cr )

STATIC PROCEDURE FWriteLine( nh, c )
   FWrite( nh, c + hb_eol() )
   // hb_OutDebug( c + hb_eol() )
   RETURN

STATIC FUNCTION Arguments( oErr )
   LOCAL xArg, cArguments := ""
   IF HB_ISARRAY( oErr:Args )
      FOR EACH xArg IN oErr:Args
         cArguments += " [" + Str( xArg:__EnumIndex(), 2 ) + "] = Type: " + ValType( xArg )

         IF xArg != NIL
            cArguments +=  " Val: " + hb_CStr( xArg )
         ENDIF
      NEXT
   ENDIF
   RETURN cArguments

Thanks so much for your help.

JUN
Philippines








--
--
You received this message because you are subscribed to the Google
Groups "Harbour Users" group.
Unsubscribe: harbour-users+unsubscribe@googlegroups.com
Web: http://groups.google.com/group/harbour-users

--- You received this message because you are subscribed to the Google Groups "Harbour Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to harbour-users+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Davaoz Best

unread,
Aug 19, 2013, 1:46:50 AM8/19/13
to harbou...@googlegroups.com
Hi,

"Error.Log" should be in lower case: "error.log"

Sorry.

Jun

Davaoz Best

unread,
Aug 19, 2013, 4:42:15 AM8/19/13
to harbou...@googlegroups.com
Hi All,

Have just seen Harbour's displaying on screen an error message in text mode, which is very comprehensive. Observe the following:

BaseXXX something - description of error here, then
Called from...func1(190)
Called from...func2(200)
Called from...func3(233)

My question is: Why not produce an error log (text file) automatically just like Xbase++ as I mentioned before? Is there not wisdom and sheer practicality by saving automatically into a text-file an error that is displayed on screen? Why not include in the static function known as DefError() in ErrorSys.Prg, a LogError(oError) or whatever_func() for example, of which purpose is to save to disk the error message displayed on screen (or even those not displayed on screen as in -gtwvt (without a special code)!)?

There are tons of circumstances where an internal run-time error does occur beyond the scope of the programmer's calculation and imagination. Harbour's built-in error message engine has an error-detection capability that is much more swift, superior, and "mechanical." Saving an error message to disk therefore, to my opinion, is indispensable, rather than writing a save-to-disk-the-error-message code endlessly. 

We are told that we can save to disk hbmk2's screen output by using the following example:

hbmk2 try.hbp -rebuild 1> log.txt 2>&1

(from here: http://www.myharbournotes.blogspot.com/2012/06/how-to-save-hbmk2s-error-messages.html).

Honestly, I cannot see the reason of Harbour's ignoring this native-Xbase++'s error-logging feature.

It is just my opinion. Any idea? 

JUN

Alex Strickland

unread,
Aug 19, 2013, 5:04:57 AM8/19/13
to harbou...@googlegroups.com
On 2013-08-19 10:42 AM, Davaoz Best wrote:

> Honestly, I cannot see the reason of Harbour's ignoring this
> native-Xbase++'s error-logging feature.

A major goal of the project is Clipper compatibility.

As you've seen you are free to change the default behaviour.

--
Regards
Alex

Klas Engwall

unread,
Aug 19, 2013, 3:32:22 PM8/19/13
to harbou...@googlegroups.com
Hi Jun,

> Have just seen Harbour's displaying on screen an error message in text
> mode, which is very comprehensive. Observe the following:
>
> BaseXXX something - description of error here, then
> Called from...func1(190)
> Called from...func2(200)
> Called from...func3(233)
>
> My question is: Why not produce an error log (text file) automatically
> just like Xbase++ as I mentioned before? Is there not wisdom and sheer
> practicality by saving automatically into a text-file an error that is
> displayed on screen? Why not include in the static function known as
> DefError() in ErrorSys.Prg, a LogError(oError) or whatever_func() for
> example, of which purpose is to save to disk the error message displayed
> on screen (or even those not displayed on screen as in -gtwvt (without a
> special code)!)?

Does every Harbour application programmer want error logs saved in the
systems of their customers? I don't think so. Do many Harbour users want
that? Yes, probably. So which is better? To give everyone an opportunity
to easily add whatever code he/she wants in order to fulfill that wish
or to force those who do not want it to remove code from errsys.prg? How
many minutes did it take for you to borrow the xhb code and add it to
your project? Was it difficult?

> There are tons of circumstances where an internal run-time error does
> occur beyond the scope of the programmer's calculation and imagination.
> Harbour's built-in error message engine has an error-detection
> capability that is much more swift, superior, and "mechanical."

And it is very easy to add it. Also, you have a choice of just
duplicating the screen messages in a file, to use the very comprehensive
xhb compatible version, or to do something inbetween. Which version
should be automatic in your opininon, and why that particular version?
This is IMHO something that the application programmer should decide.

> Saving
> an error message to disk therefore, to my opinion, is indispensable,
> rather than writing a save-to-disk-the-error-message code endlessly.

What do you mean with "endlessly"? You do it once unless you want
different functionality in different projects.

> We are told that we can save to disk hbmk2's screen output by using the
> following example:
>
> hbmk2 try.hbp -rebuild 1> log.txt 2>&1

That is just simple standard redirection like with DIR and many other
utilities in the operating system. And hbmk2 is written in Harbour, so
you can do the same if you write application that spit out log messages
to the screen.

> Honestly, I cannot see the reason of Harbour's ignoring this
> native-Xbase++'s error-logging feature.

It is not a Harbour goal to be Xbase++ compatible but to be Clipper
compatible.

Regards,
Klas

Davaoz Best

unread,
Aug 20, 2013, 12:11:08 AM8/20/13
to harbou...@googlegroups.com
Hi Klas,

Does every Harbour application programmer want error logs saved in the systems of their customers? I don't think so. Do many Harbour users want that? Yes, probably. So which is better? To give everyone an opportunity to easily add whatever code he/she wants in order to fulfill that wish or to force those who do not want it to remove code from errsys.prg? How many minutes did it take for you to borrow the xhb code and add it to your project? Was it difficult?

The idea "To give everyone an opportunity to easily add whatever code" is so logical and I appreciate so much the efforts you did spend in order for us, Harbour users (particularly the newbies like myself), to understand Harbour's practical and sound architectural principle. 

Honestly however, I don't know how to ADD ONCE a saved-to-disk error log system to my application, can you help me, please?

What do you mean with "endlessly"? You do it once unless you want different functionality in different projects.

In my tests, I followed the Clipper documentation to replace DefError() with "MyError()" and then use a BEGIN/END SEQUENCE construct. My problem is how to write my error-catching routine that is capable of  writing to disk an error log file ONCE in my PROC MAIN().

In my experiment, I used the following code:


PROC MAIN
local oldErr
oldErr:=errorblock()
errorblock({|e| myerror( e, oldErr ) } )
cls
begin sequence
use xx new
end sequence
errorblock(oldErr)
return

This code hangs up when executed, but after pressing ENTER, it will spit out a text-file error log through myerror(). (However, it won't work on an application-wide scope. I did just handle the error in the code, which is the missing xx dbf.)

As mentioned in my previous post, myerror() simply looks like this:

FUNCTION MyError( e, bHandler ) // from the forum as suggested.
   Local xRes
   LogError(e) //  from xhberr.prg, which I edited to write only limited info.
   xRes:=Eval(bHandler,e)
RETURN (xres)

Based on your instruction, I used the code from contrib\xhb\xhberr.prg and removed the "x" as you told me, hence the resulting file name became "hberr.prg". I added it to my "myapp.hbp" file. My problem is that I don't know how to call it.

The following are few of the functions inside xhberr.prg. I have tested already those functions that are called by LogError(), which I employed in my test function, MyError(). I don't know if you can show me based on these functions below how to make them work for my generic error log system--"written once.".

 1)  hb_ErrorLog( cErrorLog, lErrorLogAppend )
 2)  hb_ErrorSys()
 3) STATIC FUNCTION hb_DefError( oError )
 4) hb_ErrorNew( cSubSystem, nGenCode, nSubCode, ;
      cOperation, cDescription, aArgs, ;
      cModuleName, cProcName, nProcLine )

I made an experiment of placing the code below in my PROC MAIN without BEGIN/END SEQUENCE, but it didn't work.

oldErr:=errorblock()
errorblock({|e| myerror( e, oldErr ) } )

I tried "DefError()", but the compiler said "MISSING FUNC", since DefError() in ErrorSys.Prg, just like xhb_DefError() in xhberr.prg is made STATIC.

Lastly, I don't know if it is necessary to place the above code in an INIT PROC.

You help is highly appreciated.

JUN  




Regards,
Klas

elch

unread,
Aug 20, 2013, 3:01:55 AM8/20/13
to harbou...@googlegroups.com
Hi Jun,

for a quick success:


PROC MAIN

xhb_errorsys()

cls

use notthere new

return



compile/ link it with:

hbmk2 jun.prg xhb.hbc


( the xhb.hbc links the xharbour contrib lib and it's dependencies )

-- then you have the fine Klas solution.


( right, a STATIC function can only be called from a function WITHIN the same .prg module )



+++

What xBase++ is doing, you should find in

c:\alaska\xppw32\source\sys\errorsys.prg

You can not use that directly, as xBase++ is using therin non-Clipper functions [ i.e. msgbox() ], which are unknown in Harbour.

Further they use 'SET ALTERNATE TO' to redirect console output into a file -- a third way to Rome, and there maybe more ...


regards

Rolf

Davaoz Best

unread,
Aug 20, 2013, 5:23:40 AM8/20/13
to harbou...@googlegroups.com
Hi Rolf,

Thank you so much. Clear as noonday. I'll try it. Many thanks.

JUN


--
--
You received this message because you are subscribed to the Google
Groups "Harbour Users" group.

Web: http://groups.google.com/group/harbour-users
 
---
You received this message because you are subscribed to the Google Groups "Harbour Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to harbour-user...@googlegroups.com.

Davaoz Best

unread,
Aug 20, 2013, 8:56:19 AM8/20/13
to harbou...@googlegroups.com
Hi Rolf, Klas, and Alex,

Thank you so much for your help. My problem on how to save to disk a run-time internal error in my application, commonly termed in the Harbour world as hb_out.log or whatever.log or error.log in xhb, has been finally solved based on my tests.

For the sake of other users, I followed Rolf's instruction:

Proc Main
some code...
xhb_errorsys()
use fakedbf new
return

And in my trial.hbp, I added:

xhb.hbc

The result was xHarbour's quite lengthy error.log. Seeing it the first time, I began to realize the depth of Klas's idea that it requires good judgment indeed on the part of the programmer to allow the customer to view and read such seemingly bloated error report.

During the app's development stage however, I think adding "xhb_errorsys() makes sense, more so if you will just edit and/or reduce the details loaded in function logerror() in xhberr.prg (contrib/xhb).

I pasted here this function (logerror()) for other forum users who are interested in this thread to benefit from. (I tried to comment out those not so necessary a feature to my opinion, namely, the details about the app's environment information and the so-called "Video Screen Dump."
  

STATIC FUNCTION LogError( oerr )

   LOCAL cScreen
   LOCAL cLogFile    := s_cErrorLog       // error log file name
   LOCAL lAppendLog  := s_lErrorLogAppend // .F. = create a new error log (default) .T. = append to a existing one.
   LOCAL nCols
   LOCAL nRows

   LOCAL nCount

   LOCAL nForLoop
   LOCAL cOutString

   LOCAL nHandle
   LOCAL nBytes

   LOCAL nHandle2   := F_ERROR
   LOCAL cLogFile2  := "_error.log"
   LOCAL cBuff      := ""
   LOCAL nRead

   nCols := MaxCol()
   IF nCols > 0
      nRows := MaxRow()
      cScreen := SaveScreen()
   ENDIF

   // Alert( "An error occured, Information will be ;written to error.log" )

   IF ! lAppendLog
      nHandle := FCreate( cLogFile, FC_NORMAL )
   ELSE
      IF ! hb_FileExists( cLogFile )
         nHandle := FCreate( cLogFile, FC_NORMAL )
      ELSE
         nHandle  := FCreate( cLogFile2, FC_NORMAL )
         nHandle2 := FOpen( cLogFile, FO_READ )
      ENDIF
   ENDIF


   IF nHandle < 3 .AND. !( Lower( cLogFile ) == "error.log" )
      // Force creating error.log in case supplied log file cannot
      // be created for any reason
      cLogFile := "error.log"
      nHandle := FCreate( cLogFile, FC_NORMAL )
   ENDIF

   IF nHandle < 3
   ELSE

      FWriteLine( nHandle, PadC( " xHarbour Error Log ", 79, "-" ) )
      FWriteLine( nHandle, "" )

      FWriteLine( nHandle, "Date...............: " + DToC( Date() )  )
      FWriteLine( nHandle, "Time...............: " + Time()          )

      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, "Application name...: " + hb_CmdArgArgV() )
      FWriteLine( nHandle, "Workstation name...: " + NetName() )
      FWriteLine( nHandle, "Available memory...: " + strvalue( Memory( 0 ) )  )
      FWriteLine( nHandle, "Current disk.......: " + DiskName() )
      FWriteLine( nHandle, "Current directory..: " + CurDir() )
      FWriteLine( nHandle, "Free disk space....: " + strvalue( DiskSpace() ) )
      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, "Operating system...: " + OS() )
      FWriteLine( nHandle, "xHarbour version...: " + Version() )
      FWriteLine( nHandle, "xHarbour built on..: " + hb_BuildDate() )
      FWriteLine( nHandle, "C/C++ compiler.....: " + hb_Compiler() )

      FWriteLine( nHandle, "Multi Threading....: " + iif( hb_mtvm(), "YES", "NO" ) )
      FWriteLine( nHandle, "VM Optimization....: " + strvalue( hb_VMMode() ) )

      IF hb_IsFunction( "Select" )
         FWriteLine( nHandle, "" )
         FWriteLine( nHandle, "Current Area ......:" + strvalue( Eval( hb_macroBlock( "Select()" ) ) ) )
      ENDIF

/* ???  ----- environment info

      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, PadC( " Environmental Information ", 79, "-" ) )
      FWriteLine( nHandle, "" )

      FWriteLine( nHandle, "SET ALTERNATE......: " + strvalue( Set( _SET_ALTERNATE ), .T. ) )
      FWriteLine( nHandle, "SET ALTFILE........: " + strvalue( Set( _SET_ALTFILE ) ) )
      FWriteLine( nHandle, "SET AUTOPEN........: " + strvalue( Set( _SET_AUTOPEN ), .T. ) )
      FWriteLine( nHandle, "SET AUTORDER.......: " + strvalue( Set( _SET_AUTORDER ) ) )
      FWriteLine( nHandle, "SET AUTOSHARE......: " + strvalue( Set( _SET_AUTOSHARE ) ) )

#ifdef __XHARBOUR__
      FWriteLine( nHandle, "SET BACKGROUNDTASKS: " + strvalue( Set( _SET_BACKGROUNDTASKS ), .T. ) )
      FWriteLine( nHandle, "SET BACKGROUNDTICK.: " + strvalue( Set( _SET_BACKGROUNDTICK ), .T. ) )
#endif
      FWriteLine( nHandle, "SET BELL...........: " + strvalue( Set( _SET_BELL ), .T. ) )
      FWriteLine( nHandle, "SET BLINK..........: " + strvalue( SetBlink() ) )

      FWriteLine( nHandle, "SET CANCEL.........: " + strvalue( Set( _SET_CANCEL ), .T. ) )
      FWriteLine( nHandle, "SET CENTURY........: " + strvalue( __SetCentury(), .T. ) )
      FWriteLine( nHandle, "SET COLOR..........: " + strvalue( Set( _SET_COLOR ) ) )
      FWriteLine( nHandle, "SET CONFIRM........: " + strvalue( Set( _SET_CONFIRM ), .T. ) )
      FWriteLine( nHandle, "SET CONSOLE........: " + strvalue( Set( _SET_CONSOLE ), .T. ) )
      FWriteLine( nHandle, "SET COUNT..........: " + strvalue( Set( _SET_COUNT ) ) )
      FWriteLine( nHandle, "SET CURSOR.........: " + strvalue( Set( _SET_CURSOR ) ) )

      FWriteLine( nHandle, "SET DATE FORMAT....: " + strvalue( Set( _SET_DATEFORMAT ) ) )
      FWriteLine( nHandle, "SET DBFLOCKSCHEME..: " + strvalue( Set( _SET_DBFLOCKSCHEME ) ) )
      FWriteLine( nHandle, "SET DEBUG..........: " + strvalue( Set( _SET_DEBUG ), .T. ) )
      FWriteLine( nHandle, "SET DECIMALS.......: " + strvalue( Set( _SET_DECIMALS ) ) )
      FWriteLine( nHandle, "SET DEFAULT........: " + strvalue( Set( _SET_DEFAULT ) ) )
      FWriteLine( nHandle, "SET DEFEXTENSIONS..: " + strvalue( Set( _SET_DEFEXTENSIONS ), .T. ) )
      FWriteLine( nHandle, "SET DELETED........: " + strvalue( Set( _SET_DELETED ), .T. ) )
      FWriteLine( nHandle, "SET DELIMCHARS.....: " + strvalue( Set( _SET_DELIMCHARS ) ) )
      FWriteLine( nHandle, "SET DELIMETERS.....: " + strvalue( Set( _SET_DELIMITERS ), .T. ) )
      FWriteLine( nHandle, "SET DEVICE.........: " + strvalue( Set( _SET_DEVICE ) ) )
      FWriteLine( nHandle, "SET DIRCASE........: " + strvalue( Set( _SET_DIRCASE ) ) )
      FWriteLine( nHandle, "SET DIRSEPARATOR...: " + strvalue( Set( _SET_DIRSEPARATOR ) ) )

      FWriteLine( nHandle, "SET EOL............: " + strvalue( Asc( Set( _SET_EOL ) ) ) )
      FWriteLine( nHandle, "SET EPOCH..........: " + strvalue( Set( _SET_EPOCH ) ) )
      FWriteLine( nHandle, "SET ERRORLOG.......: " + strvalue( cLogFile ) + "," + strvalue( lAppendLog ) )
#ifdef __XHARBOUR__
      FWriteLine( nHandle, "SET ERRORLOOP......: " + strvalue( Set( _SET_ERRORLOOP ) ) )
#endif
      FWriteLine( nHandle, "SET ESCAPE.........: " + strvalue( Set( _SET_ESCAPE ), .T. ) )
      FWriteLine( nHandle, "SET EVENTMASK......: " + strvalue( Set( _SET_EVENTMASK ) ) )
      FWriteLine( nHandle, "SET EXACT..........: " + strvalue( Set( _SET_EXACT ), .T. ) )
      FWriteLine( nHandle, "SET EXCLUSIVE......: " + strvalue( Set( _SET_EXCLUSIVE ), .T. ) )
      FWriteLine( nHandle, "SET EXIT...........: " + strvalue( Set( _SET_EXIT ), .T. ) )
      FWriteLine( nHandle, "SET EXTRA..........: " + strvalue( Set( _SET_EXTRA ), .T. ) )
      FWriteLine( nHandle, "SET EXTRAFILE......: " + strvalue( Set( _SET_EXTRAFILE ) ) )

      FWriteLine( nHandle, "SET FILECASE.......: " + strvalue( Set( _SET_FILECASE ) ) )
      FWriteLine( nHandle, "SET FIXED..........: " + strvalue( Set( _SET_FIXED ), .T. ) )
      FWriteLine( nHandle, "SET FORCEOPT.......: " + strvalue( Set( _SET_FORCEOPT ), .T. ) )

      FWriteLine( nHandle, "SET HARDCOMMIT.....: " + strvalue( Set( _SET_HARDCOMMIT ), .T. ) )

      FWriteLine( nHandle, "SET IDLEREPEAT.....: " + strvalue( Set( _SET_IDLEREPEAT ), .T. ) )
      FWriteLine( nHandle, "SET INSERT.........: " + strvalue( Set( _SET_INSERT ), .T. ) )
      FWriteLine( nHandle, "SET INTENSITY......: " + strvalue( Set( _SET_INTENSITY ), .T. ) )

      FWriteLine( nHandle, "SET LANGUAGE.......: " + strvalue( Set( _SET_LANGUAGE ) ) )

      FWriteLine( nHandle, "SET MARGIN.........: " + strvalue( Set( _SET_MARGIN ) ) )
      FWriteLine( nHandle, "SET MBLOCKSIZE.....: " + strvalue( Set( _SET_MBLOCKSIZE ) ) )
      FWriteLine( nHandle, "SET MCENTER........: " + strvalue( Set( _SET_MCENTER ), .T. ) )
      FWriteLine( nHandle, "SET MESSAGE........: " + strvalue( Set( _SET_MESSAGE ) ) )
      FWriteLine( nHandle, "SET MFILEEXT.......: " + strvalue( Set( _SET_MFILEEXT ) ) )

      FWriteLine( nHandle, "SET OPTIMIZE.......: " + strvalue( Set( _SET_OPTIMIZE ), .T. ) )
#ifdef __XHARBOUR__
      FWriteLine( nHandle, "SET OUTPUTSAFETY...: " + strvalue( Set( _SET_OUTPUTSAFETY ), .T. ) )
#endif

      FWriteLine( nHandle, "SET PATH...........: " + strvalue( Set( _SET_PATH ) ) )
      FWriteLine( nHandle, "SET PRINTER........: " + strvalue( Set( _SET_PRINTER ), .T. ) )
#ifdef __XHARBOUR__
      FWriteLine( nHandle, "SET PRINTERJOB.....: " + strvalue( Set( _SET_PRINTERJOB ) ) )
#endif
      FWriteLine( nHandle, "SET PRINTFILE......: " + strvalue( Set( _SET_PRINTFILE ) ) )

      FWriteLine( nHandle, "SET SCOREBOARD.....: " + strvalue( Set( _SET_SCOREBOARD ), .T. ) )
      FWriteLine( nHandle, "SET SCROLLBREAK....: " + strvalue( Set( _SET_SCROLLBREAK ), .T. ) )
      FWriteLine( nHandle, "SET SOFTSEEK.......: " + strvalue( Set( _SET_SOFTSEEK ), .T. ) )
      FWriteLine( nHandle, "SET STRICTREAD.....: " + strvalue( Set( _SET_STRICTREAD ), .T. ) )

#ifdef __XHARBOUR__
      FWriteLine( nHandle, "SET TRACE..........: " + strvalue( Set( _SET_TRACE ), .T. ) )
      FWriteLine( nHandle, "SET TRACEFILE......: " + strvalue( Set( _SET_TRACEFILE ) ) )
      FWriteLine( nHandle, "SET TRACESTACK.....: " + strvalue( Set( _SET_TRACESTACK ) ) )
#endif
      FWriteLine( nHandle, "SET TRIMFILENAME...: " + strvalue( Set( _SET_TRIMFILENAME ) ) )

      FWriteLine( nHandle, "SET TYPEAHEAD......: " + strvalue( Set( _SET_TYPEAHEAD ) ) )

      FWriteLine( nHandle, "SET UNIQUE.........: " + strvalue( Set( _SET_UNIQUE ), .T. ) )

      FWriteLine( nHandle, "SET VIDEOMODE......: " + strvalue( Set( _SET_VIDEOMODE ) ) )

      FWriteLine( nHandle, "SET WRAP...........: " + strvalue( Set( _SET_WRAP ), .T. ) )


*/


      FWriteLine( nHandle, "" )

      IF nCols > 0
         FWriteLine( nHandle, PadC( "Detailed Work Area Items", nCols, "-" ) )
         FWriteLine( nHandle, PadC( " Internal Error Handling Information  ", nCols, "-" ) )
      ELSE
         FWriteLine( nHandle, " Internal Error Handling Information  " )
      ENDIF
      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, "Subsystem Call ....: " + oErr:subsystem() )
      FWriteLine( nHandle, "System Code .......: " + strvalue( oErr:suBcode() ) )
      FWriteLine( nHandle, "Default Status ....: " + strvalue( oerr:candefault() ) )
      FWriteLine( nHandle, "Description .......: " + oErr:description() )
      FWriteLine( nHandle, "Operation .........: " + oErr:operation() )
      FWriteLine( nHandle, "Arguments .........: " + Arguments( oErr ) )
      FWriteLine( nHandle, "Involved File .....: " + oErr:filename() )
      FWriteLine( nHandle, "Dos Error Code ....: " + strvalue( oErr:oscode() ) )

#ifdef __XHARBOUR__
#ifdef HB_THREAD_SUPPORT
      FWriteLine( nHandle, "Running threads ...: " + strvalue( oErr:RunningThreads() ) )
      FWriteLine( nHandle, "VM thread ID ......: " + strvalue( oErr:VmThreadId() ) )
      FWriteLine( nHandle, "OS thread ID ......: " + strvalue( oErr:OsThreadId() ) )
#endif
#endif

      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, " Trace Through:" )
      FWriteLine( nHandle, "----------------" )

      FWriteLine( nHandle, PadR( ProcName(), 21 ) + " : " + Transform( ProcLine(), "999,999" ) + " in Module: " + ProcFile() )

      nCount := 3
      WHILE ! Empty( ProcName( ++nCount ) )
         FWriteLine( nHandle, PadR( ProcName( nCount ), 21 ) + " : " + Transform( ProcLine( nCount ), "999,999" ) + " in Module: " + ProcFile( nCount ) )
      ENDDO

      FWriteLine( nHandle, "" )
      FWriteLine( nHandle, "" )

/* ????
      IF HB_ISSTRING( cScreen )
         FWriteLine( nHandle, PadC( " Video Screen Dump ", nCols, "#" ) )
         FWriteLine( nHandle, "" )
         FWriteLine( nHandle, "+" + Replicate( "-", nCols + 1 ) + "+" )
         FOR nCount := 0 TO nRows
            cOutString := ""
            FOR nForLoop := 0 TO nCols
               cOutString += __XSaveGetChar( cScreen, nCount * ( nCols + 1 ) + nForLoop )
            NEXT
            FWriteLine( nHandle, "|" + cOutString + "|" )
         NEXT
         FWriteLine( nHandle, "+" + Replicate( "-", nCols + 1 ) + "+" )
         FWriteLine( nHandle, "" )
         FWriteLine( nHandle, "" )
      ELSE
         FWriteLine( nHandle, " Video Screen Dump not available" )
      ENDIF
*/
   RETURN .F.

Thanks again.

Best regards,

JUN










Reply all
Reply to author
Forward
0 new messages