0031731: Draw Harness - colorize errors and exception messages
[occt.git] / src / Draw / Draw_Interpretor.cxx
CommitLineData
b311480e 1// Created on: 1995-02-23
2// Created by: Remi LEQUETTE
3// Copyright (c) 1995-1999 Matra Datavision
973c2be1 4// Copyright (c) 1999-2014 OPEN CASCADE SAS
b311480e 5//
973c2be1 6// This file is part of Open CASCADE Technology software library.
b311480e 7//
d5f74e42 8// This library is free software; you can redistribute it and/or modify it under
9// the terms of the GNU Lesser General Public License version 2.1 as published
973c2be1 10// by the Free Software Foundation, with special exception defined in the file
11// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
12// distribution for complete text of the license and disclaimer of any warranty.
b311480e 13//
973c2be1 14// Alternatively, this file may be used under the terms of Open CASCADE
15// commercial license or contractual agreement.
7fd59977 16
dda67c1c 17#include <Draw_Interpretor.hxx>
d99f0355 18
7fd59977 19#include <Draw_Appli.hxx>
d99f0355 20#include <Message.hxx>
21#include <Message_PrinterOStream.hxx>
22#include <OSD.hxx>
23#include <OSD_File.hxx>
24#include <OSD_Path.hxx>
25#include <OSD_Process.hxx>
7fd59977 26#include <Standard_SStream.hxx>
27#include <Standard_RangeError.hxx>
28#include <Standard_ErrorHandler.hxx>
29#include <Standard_Macro.hxx>
7fd59977 30#include <TCollection_AsciiString.hxx>
31#include <TCollection_ExtendedString.hxx>
32
33#include <string.h>
7fd59977 34#include <tcl.h>
e05c25c1 35#include <fcntl.h>
03155c18 36#ifndef _WIN32
37#include <unistd.h>
38#endif
7fd59977 39
aa02980d 40// for capturing of cout and cerr (dup(), dup2())
7c65581d 41#ifdef _WIN32
aa02980d 42#include <io.h>
e05c25c1 43#include <sys/stat.h>
aa02980d 44#endif
aa02980d 45
46#if ! defined(STDOUT_FILENO)
47#define STDOUT_FILENO fileno(stdout)
48#endif
49#if ! defined(STDERR_FILENO)
50#define STDERR_FILENO fileno(stderr)
51#endif
52
7fd59977 53#if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 1)))
54#define TCL_USES_UTF8
55#endif
56
aa02980d 57// logging helpers
58namespace {
59 void dumpArgs (Standard_OStream& os, int argc, const char *argv[])
60 {
61 for (int i=0; i < argc; i++)
62 os << argv[i] << " ";
04232180 63 os << std::endl;
aa02980d 64 }
65
66 void flush_standard_streams ()
67 {
68 fflush (stderr);
69 fflush (stdout);
04232180 70 std::cerr << std::flush;
71 std::cout << std::flush;
aa02980d 72 }
73
e05c25c1 74 int capture_start (int theFDStd, int theFDLog)
aa02980d 75 {
e05c25c1 76 Standard_ASSERT_RETURN (theFDLog >= 0, "Invalid descriptor of log file", -1);
77
78 // Duplicate a file descriptor of the standard stream to be able to restore output to it later
79 int aFDSave = dup (theFDStd);
80 if (aFDSave < 0)
81 {
82 perror ("Error capturing standard stream to log: dup() returned");
83 return -1;
84 }
85
86 // Redirect the stream to the log file
87 if (dup2 (theFDLog, theFDStd) < 0)
aa02980d 88 {
e05c25c1 89 close (aFDSave);
90 perror ("Error capturing standard stream to log: dup2() returned");
95e05159 91 return -1;
aa02980d 92 }
93
e05c25c1 94 // remember saved file descriptor of standard stream
95 return aFDSave;
aa02980d 96 }
97
e05c25c1 98 void capture_end (int theFDStd, int& theFDSave)
aa02980d 99 {
e05c25c1 100 if (theFDSave < 0)
e9b037ef 101 return;
102
aa02980d 103 // restore normal descriptors of console stream
e05c25c1 104 if (dup2(theFDSave, theFDStd) < 0)
aa02980d 105 {
e05c25c1 106 perror ("Error returning capturing standard stream to log: dup2() returned");
107 return;
aa02980d 108 }
109
e05c25c1 110 // close saved file descriptor
111 close(theFDSave);
112 theFDSave = -1;
aa02980d 113 }
95e05159 114
68858c7d 115} // anonymous namespace
aa02980d 116
f996b507 117static Standard_Integer CommandCmd (ClientData theClientData, Tcl_Interp* interp, Standard_Integer argc, const char* argv[])
7fd59977 118{
d99f0355 119 Standard_Integer code = TCL_OK;
dda67c1c 120 Draw_Interpretor::CallBackData* aCallback = (Draw_Interpretor::CallBackData* )theClientData;
121 Draw_Interpretor& di = *(aCallback->myDI);
aa02980d 122
123 // log command execution, except commands manipulating log itself and echo
124 Standard_Boolean isLogManipulation = (strcmp (argv[0], "dlog") == 0 ||
125 strcmp (argv[0], "decho") == 0);
126 Standard_Boolean doLog = (di.GetDoLog() && ! isLogManipulation);
127 Standard_Boolean doEcho = (di.GetDoEcho() && ! isLogManipulation);
aa02980d 128
129 // flush cerr and cout
130 flush_standard_streams();
131
132 // capture cout and cerr to log
e05c25c1 133 int aFDstdout = STDOUT_FILENO;
134 int aFDstderr = STDERR_FILENO;
135 int aFDerr_save = -1;
136 int aFDout_save = -1;
aa02980d 137 if (doLog)
138 {
e05c25c1 139 aFDout_save = capture_start (aFDstdout, di.GetLogFileDescriptor());
140 aFDerr_save = capture_start (aFDstderr, di.GetLogFileDescriptor());
aa02980d 141 }
7fd59977 142
e05c25c1 143 if (doEcho || doLog)
04232180 144 dumpArgs (std::cout, argc, argv);
e05c25c1 145
aa02980d 146 // run command
d99f0355 147 try
148 {
7fd59977 149 OCC_CATCH_SIGNALS
150
8a262fa1 151 // get exception if control-break has been pressed
152 OSD::ControlBreak();
153
d9ff84e8 154 Standard_Integer fres = aCallback->Invoke ( di, argc, argv /*anArgs.GetArgv()*/ );
d99f0355 155 if (fres != 0)
156 {
7fd59977 157 code = TCL_ERROR;
d99f0355 158 }
7fd59977 159 }
d99f0355 160 catch (Standard_Failure const& anException)
161 {
7fd59977 162 // fail if Draw_ExitOnCatch is set
f996b507 163 const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
164 if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
165 {
d99f0355 166 Message::SendFail() << "An exception was caught " << anException;
57c28b61 167#ifdef _WIN32
7fd59977 168 Tcl_Exit(0);
169#else
170 Tcl_Eval(interp,"exit");
171#endif
172 }
173
7fd59977 174 Standard_SStream ss;
d99f0355 175 ss << "An exception was caught " << anException << std::ends;
7fd59977 176 Tcl_SetResult(interp,(char*)(ss.str().c_str()),TCL_VOLATILE);
7fd59977 177 code = TCL_ERROR;
178 }
f996b507 179 catch (std::exception const& theStdException)
180 {
f996b507 181 const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
182 if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
183 {
d99f0355 184 Message::SendFail() << "An exception was caught " << theStdException.what() << " [" << typeid(theStdException).name() << "]";
f996b507 185 #ifdef _WIN32
186 Tcl_Exit (0);
187 #else
188 Tcl_Eval (interp, "exit");
189 #endif
190 }
191
f996b507 192 Standard_SStream ss;
d99f0355 193 ss << "An exception was caught " << theStdException.what() << " [" << typeid(theStdException).name() << "]" << std::ends;
194 Tcl_SetResult(interp,(char*)(ss.str().c_str()),TCL_VOLATILE);
f996b507 195 code = TCL_ERROR;
196 }
197 catch (...)
198 {
f996b507 199 const char* toExitOnCatch = Tcl_GetVar (interp, "Draw_ExitOnCatch", TCL_GLOBAL_ONLY);
200 if (toExitOnCatch != NULL && Draw::Atoi (toExitOnCatch))
201 {
d99f0355 202 Message::SendFail() << "UNKNOWN exception was caught ";
f996b507 203 #ifdef _WIN32
204 Tcl_Exit (0);
205 #else
206 Tcl_Eval (interp,"exit");
207 #endif
208 }
209
f996b507 210 Standard_SStream ss;
d99f0355 211 ss << "UNKNOWN exception was caught " << std::ends;
212 Tcl_SetResult(interp,(char*)(ss.str().c_str()),TCL_VOLATILE);
f996b507 213 code = TCL_ERROR;
214 }
aa02980d 215
e05c25c1 216 // log command result
217 if (doLog || doEcho)
218 {
219 const char* aResultStr = Tcl_GetStringResult (interp);
220 if (aResultStr != 0 && aResultStr[0] != '\0' )
221 {
222 std::cout << aResultStr << std::endl;
223 }
224 }
225
aa02980d 226 // flush streams
227 flush_standard_streams();
228
229 // end capturing cout and cerr
230 if (doLog)
231 {
e05c25c1 232 capture_end (aFDstderr, aFDerr_save);
233 capture_end (aFDstdout, aFDout_save);
aa02980d 234 }
235
7fd59977 236 return code;
237}
238
dda67c1c 239static void CommandDelete (ClientData theClientData)
7fd59977 240{
dda67c1c 241 Draw_Interpretor::CallBackData* aCallback = (Draw_Interpretor::CallBackData* )theClientData;
242 delete aCallback;
7fd59977 243}
244
245//=======================================================================
246//function : Draw_Interpretor
d99f0355 247//purpose :
7fd59977 248//=======================================================================
d99f0355 249Draw_Interpretor::Draw_Interpretor()
250: // the tcl interpreter is not created immediately as it is kept
251 // by a global variable and created and deleted before the main()
252 myInterp (NULL),
253 isAllocated (Standard_False),
254 myDoLog (Standard_False),
255 myDoEcho (Standard_False),
256 myToColorize (Standard_True),
257 myFDLog (-1)
258{
259 //
260}
7fd59977 261
d99f0355 262//=======================================================================
263//function : Draw_Interpretor
264//purpose :
265//=======================================================================
266Draw_Interpretor::Draw_Interpretor (const Draw_PInterp& theInterp)
267: myInterp (theInterp),
268 isAllocated (Standard_False),
269 myDoLog (Standard_False),
270 myDoEcho (Standard_False),
271 myToColorize (Standard_True),
272 myFDLog (-1)
7fd59977 273{
d99f0355 274 //
7fd59977 275}
276
277//=======================================================================
278//function : Init
0d969553 279//purpose : It is necessary to call this function
7fd59977 280//=======================================================================
281
282void Draw_Interpretor::Init()
283{
284 if (isAllocated)
285 Tcl_DeleteInterp(myInterp);
286 isAllocated=Standard_True;
287 myInterp=Tcl_CreateInterp();
288}
289
290//=======================================================================
d99f0355 291//function : SetToColorize
292//purpose :
7fd59977 293//=======================================================================
d99f0355 294void Draw_Interpretor::SetToColorize (Standard_Boolean theToColorize)
7fd59977 295{
d99f0355 296 myToColorize = theToColorize;
297 for (Message_SequenceOfPrinters::Iterator aPrinterIter (Message::DefaultMessenger()->Printers());
298 aPrinterIter.More(); aPrinterIter.Next())
299 {
300 if (Handle(Message_PrinterOStream) aPrinter = Handle(Message_PrinterOStream)::DownCast (aPrinterIter.Value()))
301 {
302 aPrinter->SetToColorize (Standard_False);
303 }
304 }
7fd59977 305}
306
307//=======================================================================
dda67c1c 308//function : add
309//purpose :
7fd59977 310//=======================================================================
dda67c1c 311void Draw_Interpretor::add (const Standard_CString theCommandName,
312 const Standard_CString theHelp,
313 const Standard_CString theFileName,
314 Draw_Interpretor::CallBackData* theCallback,
315 const Standard_CString theGroup)
7fd59977 316{
8de8dacd 317 Standard_ASSERT_RAISE (myInterp != NULL, "Attempt to add command to Null interpretor");
7fd59977 318
dda67c1c 319 Standard_PCharacter aName = (Standard_PCharacter )theCommandName;
320 Standard_PCharacter aHelp = (Standard_PCharacter )theHelp;
321 Standard_PCharacter aGroup = (Standard_PCharacter )theGroup;
322 Tcl_CreateCommand (myInterp, aName, CommandCmd, (ClientData )theCallback, CommandDelete);
7fd59977 323
324 // add the help
dda67c1c 325 Tcl_SetVar2 (myInterp, "Draw_Helps", aName, aHelp, TCL_GLOBAL_ONLY);
326 Tcl_SetVar2 (myInterp, "Draw_Groups", aGroup, aName,
327 TCL_GLOBAL_ONLY | TCL_APPEND_VALUE | TCL_LIST_ELEMENT);
938a360f 328
d33dea30 329 // add path to source file (keep not more than two last subdirectories)
dda67c1c 330 if (theFileName == NULL
331 || *theFileName == '\0')
332 {
333 return;
334 }
335
336 OSD_Path aPath (theFileName);
d33dea30 337 Standard_Integer nbTrek = aPath.TrekLength();
dda67c1c 338 for (Standard_Integer i = 2; i < nbTrek; ++i)
339 {
d33dea30 340 aPath.RemoveATrek (1);
dda67c1c 341 }
342 aPath.SetDisk ("");
343 aPath.SetNode ("");
d33dea30
PK
344 TCollection_AsciiString aSrcPath;
345 aPath.SystemName (aSrcPath);
36cc2619 346 if (aSrcPath.Value(1) == '/')
347 aSrcPath.Remove(1);
dda67c1c 348 Tcl_SetVar2 (myInterp, "Draw_Files", aName, aSrcPath.ToCString(), TCL_GLOBAL_ONLY);
7fd59977 349}
350
7fd59977 351//=======================================================================
352//function : Remove
353//purpose :
354//=======================================================================
355
356Standard_Boolean Draw_Interpretor::Remove(Standard_CString const n)
357{
358 Standard_PCharacter pN;
359 //
360 pN=(Standard_PCharacter)n;
361
362 Standard_Integer result = Tcl_DeleteCommand(myInterp,pN);
363 return result == 0;
364}
365
366//=======================================================================
367//function : Result
368//purpose :
369//=======================================================================
370
371Standard_CString Draw_Interpretor::Result() const
372{
373#if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 5)))
374 return Tcl_GetStringResult(myInterp);
375#else
376 return myInterp->result;
377#endif
378}
379
380//=======================================================================
381//function : Reset
382//purpose :
383//=======================================================================
384
385void Draw_Interpretor::Reset()
386{
387 Tcl_ResetResult(myInterp);
388}
389
390//=======================================================================
391//function : Append
392//purpose :
393//=======================================================================
394
395Draw_Interpretor& Draw_Interpretor::Append(const Standard_CString s)
396{
7fd59977 397 Tcl_AppendResult(myInterp,s,(Standard_CString)0);
7fd59977 398 return *this;
399}
400
401//=======================================================================
402//function : Append
403//purpose :
404//=======================================================================
405
406Draw_Interpretor& Draw_Interpretor::Append(const TCollection_AsciiString& s)
407{
408 return Append (s.ToCString());
409}
410
411//=======================================================================
412//function : Append
413//purpose :
414//=======================================================================
415
416Draw_Interpretor& Draw_Interpretor::Append(const TCollection_ExtendedString& theString)
417{
418#ifdef TCL_USES_UTF8
419 // Convert string to UTF-8 format for Tcl
420 char *str = new char[theString.LengthOfCString()+1];
421 theString.ToUTF8CString (str);
422 Tcl_AppendResult ( myInterp, str, (Standard_CString)0 );
423 delete[] str;
424#else
425 // put as ascii string, replacing non-ascii characters by '?'
426 TCollection_AsciiString str (theString, '?');
427 Tcl_AppendResult(myInterp,str.ToCString(),(Standard_CString)0);
428#endif
429 return *this;
430}
431
432//=======================================================================
433//function : Append
434//purpose :
435//=======================================================================
436
437Draw_Interpretor& Draw_Interpretor::Append(const Standard_Integer i)
438{
439 char c[100];
91322f44 440 Sprintf(c,"%d",i);
7fd59977 441 Tcl_AppendResult(myInterp,c,(Standard_CString)0);
442 return *this;
443}
444
445//=======================================================================
446//function : Append
447//purpose :
448//=======================================================================
449
450Draw_Interpretor& Draw_Interpretor::Append(const Standard_Real r)
451{
452 char s[100];
91322f44 453 Sprintf(s,"%.17g",r);
7fd59977 454 Tcl_AppendResult(myInterp,s,(Standard_CString)0);
455 return *this;
456}
457
458//=======================================================================
459//function : Append
460//purpose :
461//=======================================================================
462
463Draw_Interpretor& Draw_Interpretor::Append(const Standard_SStream& s)
464{
7fd59977 465 return Append (s.str().c_str());
7fd59977 466}
467
468//=======================================================================
469//function : AppendElement
470//purpose :
471//=======================================================================
472
473void Draw_Interpretor::AppendElement(const Standard_CString s)
474{
7fd59977 475 Tcl_AppendElement(myInterp, s);
7fd59977 476}
477
478//=======================================================================
479//function : Eval
480//purpose :
481//=======================================================================
482
483Standard_Integer Draw_Interpretor::Eval(const Standard_CString line)
484{
a2f76b15 485 return Tcl_Eval(myInterp,line);
7fd59977 486}
487
488
489//=======================================================================
490//function : Eval
491//purpose :
492//=======================================================================
493
494Standard_Integer Draw_Interpretor::RecordAndEval(const Standard_CString line,
495 const Standard_Integer flags)
496{
a2f76b15 497 return Tcl_RecordAndEval(myInterp,line,flags);
7fd59977 498}
499
500//=======================================================================
501//function : EvalFile
502//purpose :
503//=======================================================================
504
505Standard_Integer Draw_Interpretor::EvalFile(const Standard_CString fname)
506{
a2f76b15 507 return Tcl_EvalFile(myInterp,fname);
7fd59977 508}
509
785a9540 510//=======================================================================
511//function : PrintHelp
512//purpose :
513//=======================================================================
514
515Standard_Integer Draw_Interpretor::PrintHelp (const Standard_CString theCommandName)
516{
517 TCollection_AsciiString aCmd = TCollection_AsciiString ("help ") + theCommandName;
518 Standard_PCharacter aLinePtr = (Standard_PCharacter )aCmd.ToCString();
519 return Tcl_Eval (myInterp, aLinePtr);
520}
521
7fd59977 522//=======================================================================
523//function :Complete
524//purpose :
525//=======================================================================
526
527Standard_Boolean Draw_Interpretor::Complete(const Standard_CString line)
528{
529 Standard_PCharacter pLine;
530 //
531 pLine=(Standard_PCharacter)line;
dde68833 532 return Tcl_CommandComplete (pLine) != 0;
7fd59977 533}
534
535//=======================================================================
536//function : Destroy
537//purpose :
538//=======================================================================
539
dda67c1c 540Draw_Interpretor::~Draw_Interpretor()
7fd59977 541{
e05c25c1 542 SetDoLog (Standard_False);
543 if (myFDLog >=0)
544 {
545 close (myFDLog);
546 myFDLog = 0;
547 }
548
7fd59977 549 // MKV 01.02.05
550#if ((TCL_MAJOR_VERSION > 8) || ((TCL_MAJOR_VERSION == 8) && (TCL_MINOR_VERSION >= 4)))
551 try {
552 OCC_CATCH_SIGNALS
553 Tcl_Exit(0);
554 }
a738b534 555 catch (Standard_Failure const&) {
0797d9d3 556#ifdef OCCT_DEBUG
04232180 557 std::cout <<"Tcl_Exit have an exeption" << std::endl;
7fd59977 558#endif
559 }
560#else
57c28b61 561#ifdef _WIN32
7fd59977 562 Tcl_Exit(0);
563#endif
564#endif
565}
566
567//=======================================================================
568//function : Interp
569//purpose :
570//=======================================================================
571
572Draw_PInterp Draw_Interpretor::Interp() const
573{
574 Standard_DomainError_Raise_if (myInterp==NULL , "No call for Draw_Interpretor::Init()");
575 return myInterp;
576}
577
578void Draw_Interpretor::Set(const Draw_PInterp& PIntrp)
579{
580 if (isAllocated)
581 Tcl_DeleteInterp(myInterp);
582 isAllocated = Standard_False;
583 myInterp = PIntrp;
584}
aa02980d 585
586//=======================================================================
587//function : Logging
588//purpose :
589//=======================================================================
590
591void Draw_Interpretor::SetDoLog (Standard_Boolean doLog)
592{
e05c25c1 593 if (myDoLog == doLog)
594 return;
595
596 // create log file if not opened yet
597 if (doLog && myFDLog < 0)
598 {
599#ifdef _WIN32
600 char tmpfile[L_tmpnam + 1];
601 tmpnam(tmpfile);
602 myFDLog = open (tmpfile, O_RDWR | O_CREAT | O_EXCL | O_TEMPORARY, S_IREAD | S_IWRITE);
603#else
604 // according to Linux Filesystem Hierarchy Standard, 3.17,
605 // /tmp/ is the right directory for temporary files
606 char tmpfile[256] = "/tmp/occt_draw_XXXXXX";
607 myFDLog = mkstemp (tmpfile);
608 if (myFDLog >= 0)
609 {
610// printf ("Tmp file: %s\n", tmpfile);
611 unlink (tmpfile); // make sure the file will be deleted on close
612 }
613#endif
614 if (myFDLog < 0)
615 {
616 perror ("Error creating temporary file for capturing console output");
617 printf ("path: %s\n", tmpfile);
618 return;
619 }
620 }
621
aa02980d 622 myDoLog = doLog;
623}
624
625void Draw_Interpretor::SetDoEcho (Standard_Boolean doEcho)
626{
627 myDoEcho = doEcho;
628}
629
630Standard_Boolean Draw_Interpretor::GetDoLog () const
631{
632 return myDoLog;
633}
634
635Standard_Boolean Draw_Interpretor::GetDoEcho () const
636{
637 return myDoEcho;
638}
639
e05c25c1 640void Draw_Interpretor::ResetLog ()
aa02980d 641{
e05c25c1 642 if (myFDLog < 0)
643 return;
644
645 // flush cerr and cout, for the case if they are bound to the log
646 flush_standard_streams();
647
648 lseek (myFDLog, 0, SEEK_SET);
649
650#ifdef _WIN32
651 if (_chsize_s (myFDLog, 0) != 0)
652#else
653 if (ftruncate (myFDLog, 0) != 0)
654#endif
655 {
656 perror ("Error truncating the console log");
657 }
658}
659
660void Draw_Interpretor::AddLog (const Standard_CString theStr)
661{
662 if (myFDLog < 0 || ! theStr || ! theStr[0])
663 return;
664
665 // flush cerr and cout, for the case if they are bound to the log
666 flush_standard_streams();
667
668 // write as plain bytes
669 if (write (myFDLog, theStr, (unsigned int)strlen(theStr)) <0)
670 {
671 perror ("Error writing to console log");
672 }
673}
674
675TCollection_AsciiString Draw_Interpretor::GetLog ()
676{
677 TCollection_AsciiString aLog;
678 if (myFDLog < 0)
679 return aLog;
680
681 // flush cerr and cout
682 flush_standard_streams();
683
684 // rewind the file to its start
685 lseek (myFDLog, 0, SEEK_SET);
686
687 // read the whole log to string; this implementation
688 // is not optimized but should be sufficient
689 const int BUFSIZE = 4096;
690 char buffer[BUFSIZE + 1];
691 for (;;)
692 {
693 int nbRead = read (myFDLog, buffer, BUFSIZE);
694 if (nbRead <= 0)
695 {
696 break;
697 }
698 buffer[nbRead] = '\0';
699 aLog.AssignCat (buffer);
700 }
701
702 return aLog;
60be1f9b 703}