B4J Question [SOLVED] Getting jServer logs and show it on the IDE logs.

max123

Well-Known Member
Licensed User
Longtime User
Hi all,

as for the thread title, I'm in a situation where I start the server and need to show any request in the app log.

I have 3 questions related to this...

(Question 1)
Logs should be visualized on the IDE logs for developing purposes (this is part of a library), but when
the final app is compiled and started with a .bat file (that open prompt on Windows or someelse in terminal on Linux)
logs should visualized here as usually.

I have to get it as fast possible, the only way I found now is to use a Timer (set to 100 milliseconds),
every timer tick open a log file, read it as string and get modifications from last read. This is really not elegant way, it open read and close
the server log file 10 times every second.

Someone know a better way to do this ?
Is there a better way to redirect the server logs ?

Here is my actually code, it works and show on the IDE log every request, but it open a file a lots of times.
B4X:
Log("Start http server on port " & Port)
 
Server.Initialize("Server")
Server.Port = Port
Server.StaticFilesFolder = mRootDir
Server.LogsFileFolder = File.Combine(File.DirData("B4XWebGL"), "logs")
Server.LogsRetainDays = 1
Server.LogFormat = $"%{client}a %u %{dd/MM/yyyy HH:mm:ss ZZZ|GMT+2:00}t  [%s]  %r  (%O Bytes)      SOURCE: %{Referer}i :: %{User-Agent}i"$
Server.AddHandler("/MainPage", "MyPageHandler", False) ' Handle main page
Server.Start
    
TimerRequestsLog.Initialize("TimerRequestsLog", 100)
TimerRequestsLog.Enabled = True
Log($"Server started"$)

Private Sub TimerRequestsLog_Tick
    Try
        If mLogOldDate <> DateTime.Date(DateTime.Now) Then
            mLogsFileFolder = Server.LogsFileFolder
            mLogsFileName = "b4j-" & DateTime.Date(DateTime.Now) & ".request.log"
 
            If File.Exists(mLogsFileFolder, mLogsFileName) = False Then
 
                Dim dayAfter As String = "b4j-" & DateTime.Date(DateTime.Now + DateTime.TicksPerDay) & ".request.log"
                Dim dayBefore As String = "b4j-" & DateTime.Date(DateTime.Now - DateTime.TicksPerDay) & ".request.log"
'                Log(dayAfter)
'                Log(dayBefore)
 
                If File.Exists(mLogsFileFolder, dayAfter) Then
                    mLogsFileName = dayAfter
                Else if File.Exists(mLogsFileFolder, dayBefore) Then
                    mLogsFileName = dayBefore
                Else
                    LogErr("The server log file do not exist in " & mLogsFileFolder)
                    return
                End If
            End If
 
            Log("Server Log file name: " & mLogsFileName)
            Log("Server Log file path: " & File.Combine(mLogsFileFolder, mLogsFileName))
 
            mLogOldDate = DateTime.Date(DateTime.Now)
 
            old = File.ReadString(mLogsFileFolder, mLogsFileName)
 
            If old.Length > 0 Then
                Log(" ") : Log("INITIAL FILE LOG:")
                Log(old)
                Log("END OF INITIAL FILE LOG") : Log(" ")
            Else
                Log("Server Log File actually do not contain logs")
            End If
        End If
 
        If File.Exists(mLogsFileFolder, mLogsFileName) = False Then Return
        new = File.ReadString(mLogsFileFolder, mLogsFileName)
 
'    If new.Length <> old.Length Then
'        LogString = new.SubString(old.Length)
'        LogColor(LogString, 0xFF0000FF)
'        old = new
'    End If
 
        If new.Length <> old.Length Then
            LogString = new.SubString(old.Length)
            LogString = LogString.Replace(" HTTP/1.1", "")
            Dim cp() As String = Regex.Split(CRLF, LogString)
            For Each s As String In cp
'            If s.Contains(".html") Then
                Dim s2 As String = s.SubString2(0, s.LastIndexOf("SOURCE:"))
                If s2.Contains(".html") Or s2.ToLowerCase.Contains("mainpage") Then
                    If s2.Contains("[200]") Then
                        LogColor(s, xui.Color_Magenta)
                    Else If s2.Contains("[304]") Then
                        LogColor(s, 0xFFFF7F00)
                    Else If s2.Contains("[404]") Then
                        LogColor(s, xui.Color_Red)
                    End If
                Else
                    If s2.Contains("[200]") Then
                        LogColor(s, xui.Color_Blue)
                    Else If s2.Contains("[304]") Then
                        LogColor(s, 0xFFFF7F00)
                    Else If s2.Contains("[404]") Then
                        LogColor(s, xui.Color_Red)
                    End If
                End If
            Next

            old = new
        End If
    Catch
        Log(LastException.Message)
    End Try
End Sub
Here my log:
(Question 2)
Here I use LogColor to show my log requests, but what happen if the log is visualized on the command prompt instead of the B4X IDE ?
It is just used as normal Log by printing all the same color ? Or may not printed at all ?

(Question 3)
I think I've found a Jetty Server bug but I'm not sure. As you can see from the code, I set GMT+2.00 in the server log preferences, to set a right time offset
(seem it default use GMT+0.00) but for some strange reasons if I start the server eg. at 1.00 of night of eg. date 10 August (here in Italy), the server
do not create a new file, it just reuse the log file for a day before, so for a day 9 August even if current date is 10 August.
Even if the server is already started, it do not create new log file at 12:00 PM when the day change.

This is a reason that in my code I check the current day log, if not exist I try to get the day after, if not exist I check the day before.
For my use the day before something is necessary, but because this is a library I have to release on the forum, I searched to adapt it for any location checking even the day after. Any way to fix it ?

Many thanks for any help.
 
Last edited:
Solution
Here I use LogColor to show my log requests, but what happen if the log is visualized on the command prompt instead of the B4X IDE ?
It is just used as normal Log by printing all the same color ? Or may not printed at all ?
It will be printed non-color and with some "random" prefix.

Adding another log handler that prints to the logs:

1. Add this code to the main module:
B4X:
Private Sub AddLogHandler
    Dim server As JavaObject = srvr.As(JavaObject).GetField("server")
    Dim handler As JavaObject = server.RunMethod("getHandler", Null)
    If GetType(handler) = "org.eclipse.jetty.server.handler.gzip.GzipHandler" Then
        handler = handler.RunMethod("getHandler", Null)
    End If
    Dim handlers() As Object =...

Erel

B4X founder
Staff member
Licensed User
Longtime User
Here I use LogColor to show my log requests, but what happen if the log is visualized on the command prompt instead of the B4X IDE ?
It is just used as normal Log by printing all the same color ? Or may not printed at all ?
It will be printed non-color and with some "random" prefix.

Adding another log handler that prints to the logs:

1. Add this code to the main module:
B4X:
Private Sub AddLogHandler
    Dim server As JavaObject = srvr.As(JavaObject).GetField("server")
    Dim handler As JavaObject = server.RunMethod("getHandler", Null)
    If GetType(handler) = "org.eclipse.jetty.server.handler.gzip.GzipHandler" Then
        handler = handler.RunMethod("getHandler", Null)
    End If
    Dim handlers() As Object = handler.RunMethod("getHandlers", Null)
    Dim raw As String =  Me.As(JavaObject) 'ignore
    Dim PackageName As String = raw.SubString(raw.LastIndexOf(" ") + 1)
    Dim MyLogWriter As JavaObject
    MyLogWriter.InitializeNewInstance(PackageName & "$SimpleLogWriter", Null)
    Dim CustomRequestLog As JavaObject
    CustomRequestLog.InitializeNewInstance("org.eclipse.jetty.server.CustomRequestLog", Array(MyLogWriter, $"%{client}a - %u %t "%r" %s %O "%{Referer}i" "%{User-Agent}i""$))
   
    Dim LogHandler As JavaObject
    LogHandler.InitializeNewInstance("org.eclipse.jetty.server.handler.RequestLogHandler", Null)
    LogHandler.RunMethod("setRequestLog", Array(CustomRequestLog))
    Dim NewHandlersObjects(handlers.Length + 1) As Object
    Bit.ArrayCopy(handlers, 0, NewHandlersObjects, 0, handlers.Length)
    NewHandlersObjects(handlers.Length) = LogHandler
    Dim NewHandlers As JavaObject
    NewHandlers.InitializeArray("org.eclipse.jetty.server.Handler", NewHandlersObjects)
    handler.RunMethod("setHandlers", Array(NewHandlers))
End Sub


#if Java
public static class SimpleLogWriter implements org.eclipse.jetty.server.RequestLog.Writer {
     public void write(String requestEntry) throws java.io.IOException {
         System.out.println(requestEntry);
     }
}
#End If

2.
B4X:
srvr.As(JavaObject).SetField("MutableHandleCollection", True) 'requires jServer v4.02+
srvr.Start
AddLogHandler


********************************
jServer 4.02 allows setting the logs timezone:
B4X:
srvr.LogsTimezone = "GMT+3"
I haven't fully tested it so report your findings.

jServer v4.02 is attached.
 

Attachments

  • jServer.zip
    49.6 KB · Views: 56
Upvote 0
Solution

max123

Well-Known Member
Licensed User
Longtime User
Thank you Erel,

I will try your advices.?
And many thanks for jServer v4.02 that in B4J 10.00 is v4.01.?

I will report here my results.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
I replaced all as your advices,
the project run, the server start but in the AddLogHandler I've an error on this line:
B4X:
MyLogWriter.InitializeNewInstance(PackageName & "$SimpleLogWriter", Null)
probably depends on PackageName ? It refer to b4xcollections that is null.

Here my log:
Here my last code, I changed the Server JavaObject name to joServer and the srvr with Server that refer to a server in the project.
I added as your advices the log Timezone, but I've a question here, I have to set both, even inside the Server.LogFormat ?
B4X:
Server.LogFormat = $"%{client}a %u %{dd/MM/yyyy HH:mm:ss ZZZ|GMT+2:00}t  [%s]  %r  (%O Bytes)    SOURCE: %{Referer}i :: %{User-Agent}i"$
Server.LogsTimezone = "GMT+2"
The LogsTimezone documentation says:

The code:
B4X:
Log("Start http server on port " & Port)

Server.Initialize("Server")
Server.Port = Port
Server.StaticFilesFolder = mRootDir
Server.LogsFileFolder = File.Combine(File.DirData("B4XWebGL"), "logs")
Server.LogsRetainDays = 1
Server.LogFormat = $"%{client}a %u %{dd/MM/yyyy HH:mm:ss ZZZ|GMT+2:00}t  [%s]  %r  (%O Bytes)    SOURCE: %{Referer}i :: %{User-Agent}i"$
Server.LogsTimezone = "GMT+2"

Server.AddHandler("/MainPage", "MyPageHandler", False) ' Handle main page

Server.As(JavaObject).SetField("MutableHandleCollection", True) 'requires jServer v4.02+
Server.Start
AddLogHandler

'TimerRequestsLog.Initialize("TimerRequestsLog", 500)
'TimerRequestsLog.Enabled = True
Log($"Server started"$)

Private Sub AddLogHandler
    Dim joServer As JavaObject = Server.As(JavaObject).GetField("server")
    Dim handler As JavaObject = joServer.RunMethod("getHandler", Null)
    If GetType(handler) = "org.eclipse.jetty.server.handler.gzip.GzipHandler" Then
        handler = handler.RunMethod("getHandler", Null)
    End If
    Dim handlers() As Object = handler.RunMethod("getHandlers", Null)
 
    Dim raw As String =  Me.As(JavaObject) 'ignore
    Dim PackageName As String = raw.SubString(raw.LastIndexOf(" ") + 1)
 
    Dim MyLogWriter As JavaObject
    MyLogWriter.InitializeNewInstance(PackageName & "$SimpleLogWriter", Null)  '<<< ERROR happen on this line
    Dim CustomRequestLog As JavaObject
    CustomRequestLog.InitializeNewInstance("org.eclipse.jetty.server.CustomRequestLog", Array(MyLogWriter, $"%{client}a - %u %t "%r" %s %O "%{Referer}i" "%{User-Agent}i""$))
 
    Dim LogHandler As JavaObject
    LogHandler.InitializeNewInstance("org.eclipse.jetty.server.handler.RequestLogHandler", Null)
    LogHandler.RunMethod("setRequestLog", Array(CustomRequestLog))
    Dim NewHandlersObjects(handlers.Length + 1) As Object
    Bit.ArrayCopy(handlers, 0, NewHandlersObjects, 0, handlers.Length)
    NewHandlersObjects(handlers.Length) = LogHandler
    Dim NewHandlers As JavaObject
    NewHandlers.InitializeArray("org.eclipse.jetty.server.Handler", NewHandlersObjects)
    handler.RunMethod("setHandlers", Array(NewHandlers))
End Sub

#if Java
public static class SimpleLogWriter implements org.eclipse.jetty.server.RequestLog.Writer {
     public void write(String requestEntry) throws java.io.IOException {
         System.out.println(requestEntry);
     }
}
#End If
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
The problem is that this is a class, a library, and the PackageName may refer to the main app package name ?
This will always change on every project where the library is imported, so I cannot set it static.

How I can get class package name if this do not seem to work ?
B4X:
Dim raw As String =  Me.As(JavaObject) 'ignore
Dim PackageName As String = raw.SubString(raw.LastIndexOf(" ") + 1)
As I know this refer the SimpleLogWriter Java class, and I'm pretty sure it can be referred, I used something like this in past to manage a WebView in a class and add WebChromeClient, so used some B4X code that refer to a WebChromeClient inside a Java class. Probably used something like Application.PackageName & "$MyJavaClass", but on B4A.

EDIT:
The code used in old project is:
B4X:
Dim jo as JavaObject
jo.InitializeNewInstance(Application.PackageName & ".myB4xClass$myJavaClass", Null) ' For a class
'or
jo.InitializeNewInstance(Application.PackageName & ".b4xmainpage$myJavaClass", Null) ' For a B4X Main Page

But B4J do not have Application.PackageName.

It refer to b4xcollections that is null.
Sorry, I'm wrong here, the error has nothing to do with B4XCollections, just these lines:
B4X:
Dim raw As String =  Me.As(JavaObject) 'ignore
Dim PackageName As String = raw.SubString(raw.LastIndexOf(" ") + 1)
extract a wrong string for package name that result in:
b4xcollections=null
because raw variable string end this way:
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Ok, I found the way to go.... and I'm happy I learned something new again. This worked:
B4X:
    Dim PackageName As String = GetType(Me)
    Log("PackageName: " & PackageName) ' b4j.example.webgl
    Dim MyLogWriter As JavaObject
    MyLogWriter.InitializeNewInstance(PackageName & "$SimpleLogWriter", Null)

Now the java class is called and logs printed to B4X IDE log.

Now there are 2 problems.
1) Changing the timezone have no effect:
B4X:
Server.LogsTimezone = "GMT+2"
Tried to put it before and after Server.Start, nothing change.
The log file is same as before, just as specified on this line, and this is good:
B4X:
Server.LogFormat = $"%{client}a %u %{dd/MM/yyyy HH:mm:ss ZZZ|GMT+2:00}t  [%s]  %r  (%O Bytes)    SOURCE: %{Referer}i :: %{User-Agent}i"$
but on the B4X IDE the timezone always is GMT +0000

2) The logs on the B4X IDE do not follow the right pattern, just is a default from jServer library without changing the format.

Here some log requests printed by Java to B4X IDE log:
But I expected to be in the format I specified with Server.LogFormat command, so the same on the log file, eg:
Note that the time in these 2 examples seem the same, but really I generated at distance of 2 hours, the last on the file have right GMT and right time, the first one printed to the B4X IDE log do not.
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Please help with this last step, or I'm not able to use it.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Thanks @Erel now it works as expected.
Sorry I don't know how I missed this line that set the log pattern, may confused me a short line.

It is a mistake to post several questions / issues in the same thread. Every thread should be as focused as possible.
I know you, but so to just solve one problem I have to post more threads ?
Note, this is not a provocation, this is just a question although maybe not much related here.

I started this thread to just do one thing, log server logs on B4X IDE, and continued the thread based on
results of code to just solve the same problem until it is working, and now it's working.

Many thanks for help and for this new feature.

As reference for users this is a full working code. Here I use raiseEvent2 to call B4X sub with the log request string, in my use case, I parse it and use LogColor to show different colors based on HTTP status responses. Hope this help some users that in future need it.
B4X:
Log("Start http server on port " & Port)

Server.Initialize("Server")
Server.Port = Port
Server.StaticFilesFolder = mRootDir
Server.LogsFileFolder = File.Combine(File.DirData("B4XWebGL"), "logs")
Server.LogsRetainDays = 1
Server.LogFormat = $"%{client}a %u %{dd/MM/yyyy HH:mm:ss ZZZ|GMT+2:00}t  [%s]  %r  (%O Bytes)    SOURCE: %{Referer}i :: %{User-Agent}i"$
Server.LogsTimezone = "GMT+2"  ' Check it

Server.AddHandler("/MainPage", "MyPageHandler", False) ' Handle main page
Server.As(JavaObject).SetField("MutableHandleCollection", True) ' Requires jServer v4.02+
Server.Start
AddLogHandler                 
Log($"Server started"$)

'.....

Private Sub AddLogHandler
    Dim joServer As JavaObject = Server.As(JavaObject).GetField("server")
    Dim handler As JavaObject = joServer.RunMethod("getHandler", Null)
    If GetType(handler) = "org.eclipse.jetty.server.handler.gzip.GzipHandler" Then
        handler = handler.RunMethod("getHandler", Null)
    End If
    Dim handlers() As Object = handler.RunMethod("getHandlers", Null)
 
    Dim PackageName As String = GetType(Me)
    Log("PackageName: " & PackageName) ' b4j.example.webgl
    Dim MyLogWriter As JavaObject
    MyLogWriter.InitializeNewInstance(PackageName & "$SimpleLogWriter", Null)
 
    Dim CustomRequestLog As JavaObject
'    CustomRequestLog.InitializeNewInstance("org.eclipse.jetty.server.CustomRequestLog", Array(MyLogWriter, $"%{client}a - %u %t "%r" %s %O "%{Referer}i" "%{User-Agent}i""$))
    CustomRequestLog.InitializeNewInstance("org.eclipse.jetty.server.CustomRequestLog", Array(MyLogWriter, $"%{client}a %u %{dd/MM/yyyy HH:mm:ss ZZZ|GMT+2:00}t  [%s]  %r  (%O Bytes)   SOURCE: %{Referer}i :: %{User-Agent}i"$))
 
    Dim LogHandler As JavaObject
    LogHandler.InitializeNewInstance("org.eclipse.jetty.server.handler.RequestLogHandler", Null)
    LogHandler.RunMethod("setRequestLog", Array(CustomRequestLog))
    Dim NewHandlersObjects(handlers.Length + 1) As Object
    Bit.ArrayCopy(handlers, 0, NewHandlersObjects, 0, handlers.Length)
    NewHandlersObjects(handlers.Length) = LogHandler
    Dim NewHandlers As JavaObject
    NewHandlers.InitializeArray("org.eclipse.jetty.server.Handler", NewHandlersObjects)
    handler.RunMethod("setHandlers", Array(NewHandlers))
End Sub

Private Sub Print_ServerLog (Text As String)
    If mLogRequests = False Then Return
 
    Dim s As String = Text.SubString2(0, Text.LastIndexOf("SOURCE:"))
    If s.Contains(".html") Or s.ToLowerCase.Contains("mainpage") Then
        If s.Contains("[200]") Then
            LogColor(Text, xui.Color_Magenta)
        Else If s.Contains("[304]") Then
            LogColor(Text, 0xFFFF7F00)
        Else If s.Contains("[404]") Then
            LogColor(Text, xui.Color_Red)
        End If
    Else
        If s.Contains("[200]") Then
            LogColor(Text, xui.Color_Blue)
        Else If s.Contains("[304]") Then
            LogColor(Text, 0xFFFF7F00)
        Else If s.Contains("[404]") Then
            LogColor(Text, xui.Color_Red)
        End If
    End If
End Sub

#If JAVA
import anywheresoftware.b4a.BA.RaisesSynchronousEvents;
static BA ba;

public static class SimpleLogWriter implements org.eclipse.jetty.server.RequestLog.Writer {
     public void write(String requestEntry) throws java.io.IOException {
       //  System.out.println(requestEntry);
      
         String sub = "Print_ServerLog";
         ba.raiseEvent2(this, false, sub.toLowerCase(), true, requestEntry); 
      }
}
#End If
 

Attachments

  • Immagine 2024-08-08 135801.png
    225.6 KB · Views: 67
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Just one important note here....

In my code I posted (that is a Class), when I compiled as library, the LogColor always show single color
in the main code that use the library.

If someone have this issue, just use callSubDelayed2 to pass the text log to the calling module,
here you can then use LogColor and it will work correctly.
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…