• Add function to add to a logentry

    By Jan Schulz 2 decades ago

    Hi,



    first of all: thanks for this thingie. It's simple amazing how fast you can add logging to your app and use the outputs for debugging.



    Currently I'm only missing one thing: how to send multiple log-messages to one logentry. It would be nice if OpenLog gets a normal 'logger' functions, which would behave more or less like the Notes AgentLog, only outputting the whole thing to a logentry.



    This would be especcialy usefull, if this could be configured to save after each added message in case you have to debug a server crash…



    Thanks,



    Jan

    • Re: test the Agent (Beta) --> Test LS StackTrace Hope this helps<eom>

      By Thomas Balatka 2 decades ago
    • Re: test the Agent (Beta) --> Test LS StackTrace Hope this helps<eom>

      By Thomas Balatka 2 decades ago
    • Vote +1

      By Peter Herrmann 2 decades ago

      Great request. I have done this in my own code before but I now use OpenLog for every new app.



      For Julian: the only challenge I had to deal with was I wanted to write a TEXT field but sometimes my

      string message would end up being larger than 64Kb. I ended up flushing the current logentry to disk

      (doc.save) if the message to append to the logentry would make it >64KB. I would then start a new log

      entry and close it on Terminate. I didn't handle the case where you wanted to append one string >64Kb

      to the logentry but it wouldn't be difficult if you wanted to handle that too.

    • Class to add (Agent) logging

      By Jan Schulz 2 decades ago

      Here is a first try to add a logger. I use it to collect information from agent runs without cluttering the normal OpenLog view. Only one entry is added per agent (or more, in case the output is really long, could probably benefit from http://www.openntf.org/Projects/codebin/codebin.nsf/CodeByDate/84F37FE2DB46E185862572FD006D5428).



      I will probably add something, so that one log is used also ove serveral runs of an agent (using some idea I got from !!!HELP!!!).



      This class "reuses" some code form openlog: it would be nice to get a function into the logintems, to get the underlying doc. Currently no changes to the openLog Libs are nessesary. I used the 1.0 Version…



      It works for me, but the only tests are the ones in the TestLogger function…



      Enjoy!



      Jan



      Option Public

      Option Declare

      Use "OpenLogFunctions"



      Const PREFIX_LOGITEMNAME= "Agent Log: "

      Const LOG_SEVERITY = SEVERITY_LOW

      Const TYPE_LOG= "Message-Log"



      Const ERROR_COULD_NOT_WRITE_TO_LOG = 4001



      ' common severity types

      'Const SEVERITY_LOW = "0"

      'Const SEVERITY_MEDIUM = "1"

      'Const SEVERITY_HIGH = "2"

      '
      common event types

      'Const TYPE_ERROR = "Error"

      'Const TYPE_EVENT = "Event"





      Public Class Logger

      Private m_LogDocument As NotesDocument<br/>
      Private StartTime As NotesDateTime<br/>
      Private EndTime As NotesDateTime<br/>
      <br/>
      ' knows whether we have flushed the last message<br/>
      Private m_needsflush  As Boolean<br/>
      <br/>
      'config of this log<br/>
      Private LogName As String<br/>
      Private LogLevel  As Integer<br/>
      Private bLogMethod As Boolean<br/>
      Private bLogTime As Boolean<br/>
      Private bFlushAlways As Boolean<br/>
      <br/>
      Private currentMethodName As String<br/>
      <br/>
      Public Sub new(NewLogName As String)<br/>
          LogName =NewLogName<br/>
          'Setting Defaults       <br/>
          Me.bLogMethod = True<br/>
          Me.bLogTime = True<br/>
          'flush always could be a performance hog on slow networks...<br/>
          Me.bFlushAlways = False<br/>
      End Sub<br/>
      <br/>
      Sub Delete<br/>
          ' If we have an startTime but no EndTime, something happend as the Log was not closed properly<br/>
          If (Not (StartTime Is Nothing)) And (EndTime Is Nothing) Then<br/>
              ' Time should be usefull, but method isn't (it's &quot;DELETE&quot;)...<br/>
              Me.bLogTime = True<br/>
              Dim tmpLogMethod As Boolean<br/>
              tmpLogMethod = Me.bLogMethod<br/>
              Me.bLogMethod = False<br/>
              Call Me.LogError(&quot;Abnormal end of log: Log was not closed!&quot;, Nothing)<br/>
              bLogMethod =  tmpLogMethod<br/>
          End If<br/>
          'Save in any case<br/>
          Call Me.flush()<br/>
      End Sub<br/>
      <br/>
      Public Sub SetLogLevel (NewLevel As Integer)<br/>
          If NewLevel &gt; SEVERITY_HIGH Then<br/>
              Me.LogLevel = SEVERITY_HIGH<br/>
          Elseif NewLevel &lt; SEVERITY_LOW  Then<br/>
              Me.LogLevel = SEVERITY_LOW <br/>
          Else<br/>
              Me.LogLevel = NewLevel<br/>
          End If<br/>
      End Sub<br/>
      <br/>
      Public Sub setLogMethod(NewLogMethod As Boolean)<br/>
          bLogMethod = NewLogMethod<br/>
      End Sub<br/>
      <br/>
      Public Sub setLogTime(NewLogTime As Boolean)<br/>
          bLogTime = NewLogTime<br/>
      End Sub<br/>
      <br/>
      Public Sub setFlushAlways(NewFlushAlways As Boolean)<br/>
          bFlushAlways = NewFlushAlways<br/>
      End Sub<br/>
      <br/>
      Public Function start(message As String) As String<br/>
          ' first start the time -&gt; logAction uses it<br/>
          Set Me.StartTime = New NotesDateTime (Now ())<br/>
          Set Me.EndTime = Nothing<br/>
          ' Code Duplication with OpenLog::LogEvent<br/>
          Dim LogItem As LogItem<br/>
          Set LogItem = CreateLogItem(  PREFIX_LOGITEMNAME &amp; LogName, LOG_SEVERITY, Nothing)<br/>
          If LogItem Is Nothing Then<br/>
              Error ERROR_COULD_NOT_WRITE_TO_LOG , &quot;Could not create LogItem to log to&quot;<br/>
          End If<br/>
          Dim session As New NotesSession<br/>
          Dim strAccessLevel  As String<br/>
          Select Case session.CurrentDatabase.CurrentAccessLevel<br/>
          Case 0 : strAccessLevel = &quot;0: No Access&quot;<br/>
          Case 1 : strAccessLevel = &quot;1: Depositor&quot;<br/>
          Case 2 : strAccessLevel = &quot;2: Reader&quot;<br/>
          Case 3 : strAccessLevel = &quot;3: Author&quot;<br/>
          Case 4 : strAccessLevel = &quot;4: Editor&quot;<br/>
          Case 5 : strAccessLevel = &quot;5: Designer&quot;<br/>
          Case 6 : strAccessLevel = &quot;6: Manager&quot;<br/>
          End Select<br/>
          With LogItem<br/>
              .eventTime = Now<br/>
              .eventType = TYPE_LOG<br/>
              .userName = session.UserName<br/>
              .effName = session.EffectiveUserName<br/>
              .accessLevel = strAccessLevel<br/>
              .userRoles = Evaluate(&quot;@UserRoles&quot;)<br/>
              .clientVersion = StringToArray(Trim(session.NotesVersion) &amp; &quot;|Build &quot; &amp; session.NotesBuildVersion, &quot;|&quot;)<br/>
          End With        <br/>
          ' Code Duplication with 'OpenLog::LogItem.writeToLog'<br/>
          On Error Goto processError<br/>
          Dim logDoc As NotesDocument<br/>
          Set logDoc = LogItem.CreateLogDoc(GetLogDatabase)<br/>
          If (logDoc Is Nothing) Then<br/>
              Error ERROR_COULD_NOT_WRITE_TO_LOG , &quot;Could not create Log Document to log to&quot;<br/>
          End If<br/>
          '** make sure Depositor level users can still write/save their docs<br/>
          logDoc.~$PublicAccess = &quot;1&quot;<br/>
          Set m_LogDocument = logDoc<br/>
          Call Me.logAction(message, SEVERITY_MEDIUM, Nothing )<br/>
          <br/>
          Call Me.askForFlush()<br/>
          <br/>
          ' return the String, so i could be printed/messageboxed<br/>
          start = message<br/>
          Exit Function<br/>
      

      processError:

          ' hm... Bad! and we can't handle it with OpenLog :-(, so pass it on.<br/>
          Dim db As NotesDatabase<br/>
          Set db = getLogDatabase()<br/>
          Dim dbname As String<br/>
          If db Is Nothing Then<br/>
              dbName = &quot;No Database specified!&quot;<br/>
          Else<br/>
              dbName =  db.FileName<br/>
          End If<br/>
          Error ERROR_COULD_NOT_WRITE_TO_LOG , &quot;Cold not Write to Log-Database (&quot; &amp; dbName &amp; &quot;). [OLD ERROR: &quot; &amp; Error$ &amp; &quot;]&quot; <br/>
      End Function<br/>
      <br/>
      Public Function logDebug(msg As String, doc As NotesDocument) As String<br/>
          currentMethodName = Lsi_info(12) <br/>
          logDebug = Me.logAction(msg, SEVERITY_LOW, doc)<br/>
      End Function<br/>
      <br/>
      Public Function logError(msg As String, doc As NotesDocument) As String<br/>
          currentMethodName = Lsi_info(12) <br/>
          logError = Me.logAction(msg, SEVERITY_HIGH, doc)<br/>
      End Function<br/>
      <br/>
      Public Function logMessage(msg As String, doc As NotesDocument) As String<br/>
          currentMethodName = Lsi_info(12) <br/>
          logMessage = Me.logAction(msg, SEVERITY_MEDIUM, doc)<br/>
      End Function<br/>
      <br/>
      Public Function flush() As Boolean<br/>
          If m_LogDocument .Save(True, False) Then<br/>
              m_needsFlush = False<br/>
          End If<br/>
          flush = Not m_NeedsFlush<br/>
      End Function<br/>
      <br/>
      Public Function stop(Message As String) As String<br/>
          Set Me.EndTime = New NotesDateTime (Now ())<br/>
          Call Me.logAction(message, SEVERITY_MEDIUM, Nothing )<br/>
          Call Me.LogAction (&quot;Total running time: &quot; &amp; Cstr (EndTime.TimeDifference (StartTime)) &amp; &quot; seconds&quot;, SEVERITY_MEDIUM, Nothing  )<br/>
          Call Me.flush()<br/>
      End Function<br/>
      <br/>
      <br/>
      Private Function logAction(msg As String, severity As String, doc As NotesDocument) As String<br/>
          ' Log must be 'Started', otherweise there is no document to log to.<br/>
          If m_LogDocument Is Nothing Then<br/>
              Call Me.start(&quot;Log started automatically&quot;)<br/>
          End If<br/>
          ' only log if we must<br/>
          If severity &lt; Me.LogLevel Then<br/>
              Exit Function<br/>
          End If<br/>
          ' Add the Message to the RT Field<br/>
          Dim rtitem As NotesRichTextItem<br/>
          Set rtItem = Me.getLoggingItem()<br/>
          ' I had problems with higher Sizes: Adding one entry took longer and longer <br/>
          ' 31000 -&gt; each one doc: 2k Logmessages -&gt; 6s, 10k -&gt; 100s, 30000 -&gt; 10k: 23 doc, but 1/10 of the time<br/>
          If rtItem.ValueLength + Len(msg) + 200 &gt; 30000 Then<br/>
              Call switchToNewLogDoc()<br/>
              Set rtItem = Me.getLoggingItem()<br/>
          End If<br/>
          Call rtitem.appendText(getPrefix() &amp; msg)<br/>
          If Not doc Is Nothing Then<br/>
              Call rtitem.appendText(&quot; Associated document: notes://&quot; &amp; Cstr(doc.ParentDatabase.Server) &amp; &quot;/&quot; &amp; Cstr(doc.ParentDatabase.ReplicaID) &amp; &quot;/0/&quot; &amp; doc.UniversalID )<br/>
              ' Problems when using a DB without default view<br/>
              'Call rtitem.AppendDocLink(doc, doc.UniversalID)<br/>
              'Call rtitem.appendText(&quot;)&quot;)<br/>
          End If<br/>
          rtitem.AddNewline(1)<br/>
          Call Me.askForFlush()<br/>
      End Function<br/>
      <br/>
      Private Function getPrefix() As String<br/>
          ' returns a prefix string including a final space, if needed<br/>
          getPrefix = &quot;&quot;<br/>
          If bLogTime  Then<br/>
              getPrefix = getPrefix  &amp; Format$(Now, &quot;YYYY/MM/DD HH:NN:SS&quot;)<br/>
          End If<br/>
          If bLogMethod Then<br/>
              getPrefix = getPrefix &amp; &quot; (&quot; &amp; currentMethodName &amp;&quot;)&quot;<br/>
          End If<br/>
          If getPrefix &lt;&gt; &quot;&quot; Then<br/>
              getPrefix = getPrefix &amp; &quot;: &quot;<br/>
          End If<br/>
      End Function<br/>
      <br/>
      Private Function getLoggingItem() As NotesRichTextItem<br/>
          Dim rtField As NotesRichTextItem<br/>
          Set rtField = m_LogDocument.GetFirstItem(&quot;LogDocInfo&quot;)<br/>
          If rtField Is Nothing Then<br/>
              Set rtfield = New NotesRichTextItem(m_LogDocument, &quot;LogDocInfo&quot;)<br/>
          End If<br/>
          Set getLoggingItem= rtField<br/>
      End Function<br/>
      <br/>
      Private Sub askForFlush()<br/>
          If bFlushAlways Then<br/>
              Call Me.flush()<br/>
          Else<br/>
              m_needsFlush = True<br/>
          End If<br/>
      End Sub<br/>
      <br/>
      'to get a handle to the doc for testing purpose<br/>
      Function getLogDoc() As NotesDocument<br/>
          Set getLogDoc = m_LogDocument<br/>
      End Function<br/>
      <br/>
      Private Function switchToNewLogDoc() <br/>
          On Error Goto handleError<br/>
          Dim docOld As NotesDocument<br/>
          Dim docNew As NotesDocument<br/>
          <br/>
          Set docOld = m_LogDocument<br/>
          Set docNew = docOld.CopyToDatabase(docOld.ParentDatabase)<br/>
          Call docNew.RemoveItem(&quot;LogDocInfo&quot;)<br/>
          Call docNew.Save(True, False)<br/>
          ' calling LogAction gets a endless loop, so do it by hand<br/>
          Dim rtItem As NotesRichTextItem     <br/>
          Set rtItem = docOld.GetFirstItem(&quot;LogDocInfo&quot;)<br/>
          Call rtitem.appendText(getPrefix() &amp; &quot;Logging document full: opened new logging document:&quot; )<br/>
          Call rtitem.appendText(&quot; Associated document: notes://&quot; &amp; Cstr(docNew.ParentDatabase.Server) &amp; &quot;/&quot; &amp; Cstr(docNew.ParentDatabase.ReplicaID) &amp; &quot;/0/&quot; &amp; docNew.UniversalID )<br/>
          ' Problems when using a DB without default view<br/>
          'Call rtitem.AppendDocLink(doc, doc.UniversalID)<br/>
          'Call rtitem.appendText(&quot;)&quot;)<br/>
          rtitem.AddNewline(1)<br/>
          Call Me.flush()<br/>
          Set m_LogDocument = docNew<br/>
          ' No problem here with calling logAction<br/>
          Call Me.logAction(&quot;Continued logging from old logging document: &quot;, SEVERITY_HIGH, docOld)<br/>
          Delete docOld<br/>
          Call Me.flush()<br/>
          Exit Function<br/>
      

      handleError:

          Error ERROR_COULD_NOT_WRITE_TO_LOG , &quot;Cold not Write to Log-Database.&quot;<br/>
      End Function<br/>
      

      End Class

      Sub Initialize

      <br/>
      

      End Sub





      Public Sub TestLogger()

      Dim l As New Logger(&quot;TestLogger&quot;)<br/>
      Call l.start(&quot;Starting Test&quot;)<br/>
      Dim doc As NotesDocument<br/>
      Set doc = l.getLogDoc()<br/>
      Call l.logError(&quot;A Line with a doclink&quot;, doc)<br/>
      Call l.setLogLevel(SEVERITY_LOW)<br/>
      Call l.logDebug(&quot;This should be in the Log&quot;, Nothing)<br/>
      Call l.setLogLevel(SEVERITY_MEDIUM)<br/>
      Call l.logDebug(&quot;ERROR: This should NOT be in the Log&quot;, Nothing)<br/>
      Call l.logMessage(&quot;A normal Message&quot;, Nothing)<br/>
      Call l.setLogMethod(False)<br/>
      Call l.logMessage(&quot;A normal Message without Method Name&quot;, Nothing)<br/>
      Call l.setLogTime(False)<br/>
      Call l.logMessage(&quot;A normal Message without Anything&quot;, Nothing)<br/>
      Call l.setLogMethod(True)<br/>
      Call l.logMessage(&quot;A normal Message without Time but with Methodname&quot;, Nothing)<br/>
      Delete l<br/>
      Dim m As Integer<br/>
      Set l = New Logger(&quot;Long Running Test&quot;)<br/>
      Call l.setFlushAlways(True)<br/>
      Call l.start(&quot;start Long Running Test&quot;)<br/>
      m = 0<br/>
      While m &lt;10000<br/>
          Call l.logMessage(&quot;A normal Message &quot; &amp; Cstr(m), Nothing)<br/>
          m = m+1<br/>
      Wend<br/>
      Call l.stop(&quot;finished&quot;)<br/>
      Delete l<br/>
      Set l = New Logger(&quot;Performance commit Always&quot;)<br/>
      Call l.setFlushAlways(True)<br/>
      Call l.start(&quot;start Performance with always commit&quot;)<br/>
      m = 0<br/>
      While m &lt;2000<br/>
          Call l.logMessage(&quot;A normal Message &quot; &amp; Cstr(m), Nothing)<br/>
          m = m+1<br/>
      Wend<br/>
      Call l.stop(&quot;finished&quot;)<br/>
      Delete l<br/>
      Set l = New Logger(&quot;Performance commit ONCE&quot;)<br/>
      Call l.setFlushAlways(True)<br/>
      Call l.start(&quot;start Performance with commit ONCE&quot;)<br/>
      m = 0<br/>
      While m &lt;2000<br/>
          Call l.logMessage(&quot;A normal Message &quot; &amp; Cstr(m), Nothing)<br/>
          m = m+1<br/>
      Wend<br/>
      Call l.stop(&quot;finished&quot;)<br/>
      Delete l<br/>
      Set l = New Logger(&quot;Without start and delete&quot;)<br/>
      Call l.logMessage(&quot;A normal Message&quot;, Nothing)<br/>
      

      End Sub