Writing an API Service in Java -2 - Making Log Entries

 In this 2nd part, we will have some fun! Add some non-essential but nice features to make our API Service more visually appealing!

In my post yesterday, we learnt how to create an API Service in pure Java implementing just 2 "routes" serving basic text. The objective is to create a lightweight API service with an emphasis on performance and that means we abjure use of any heavy-weight Frameworks that add bulk to our application. We will be eventually adding code to query database and return JSON data - but that is later. Today we will focus on adding some non-essential but important visual feedback. Prof. Kelkar who taught us Assembly Language Programming in College used to call this part "Lipstick Laavne" or "applying Lipstick"! Hey that is important too!

The API service is a server application running in the background on our Server. In an ideal situation no one will ever look at it! But there are times when Server Applications dont quite work and in those cases, we need to go onto the servers to do a Visual Check! That is where Feedback  becomes important. The outside world will never see it but it lets us know exactly what is happening on our API Server! We will be writing a "Logging" function where we will document the tasks that the server is doing!

There are 2 ways to do this! The traditional way is to simply make an entry into a log file each time a request arrives. The log entry should contain not just what kind of request was made but also where it came from (IP address etc) and a summary of the response given. Depending on the volumes, Log Files can be hourly, daily or even weekly! So when we log into the server to check on our API Server, the first step is to check these logs. On most of our live Server APIs, we have daily log files with entries for each day in a separate file. More recently, we have switched to maintaining these logs in a separate mySQL database to allow for analysis and easy querying.

The 2nd way is to print out these request-response details on the Server Console (Screen) in real time as it happens. That way, when we logon to the server to do a Visual Check, we instantly get a reading on what exactly is happening at that moment without having to find and dig through Log Files! The downside to this approach is that the Screen output is temporary and there is no persistence! Also, even the act of printing to the Screen is consuming machine cycles. Not a critical thing in API servers handling low volumes. But this can become an issue on API Servers handling thousands of requests a second! As I learnt from Prof.Kelkar, we have to manage our Machine Cycles the way Indian housewives manage the monthly expenses on a limited budget! No matter how powerful your Server Hardware is, Every little but counts!

What do we do in a real life application? Well, so far I have developed API Servers handling max 2000 requests/hour. 300/hour is the average! i.e. Not High Volume! So I do BOTH! In our Log function, we make an entry into a log file/database followed by output to the screen console.

This was our main() function where we left it yesterday. It contains code to setup and launch our server to receive requests. There is no way to stop the server and there is little indication on what it is doing in real time. Let us fix this!


public class ApiService 
{
    final static int PORT=8080;

    public static void main(String[] args) throws IOException {
        HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);

        server.createContext("/", new RootHandler());           // Root URL Handler
        server.createContext("/modani", new ModaniHandler());    // Handler for 2nd URL 

        server.setExecutor(null); // creates a default executor
        server.start();
        System.out.println("Hello World!\n\r@KaleshiBua's API Service is Running and Accepting Requests from y'all!");
    }
}

Add the following section to our main() function just below the last line. Here we have added a indefinite while loop where we allow the user to intervene with a "command" entered at a prompt. and write the code to handle each command. For the purposes of this lesson, we have added 3 commands - :H: for help, :C: for Clearing screen and :X: for stopping the server and exiting the appllication. We will be using the Scanner() class from java.util for accepting User Keyboard Input. Make sure you add the relevant "import command" to your class file.

import java.util.Scanner;		//Add this import to the top of your class file.
-----------------------

	Scanner scanner = new Scanner(System.in);
	char inputCommand=0;
    while(true)
    {
    	System.out.print(" 🖝   ");
        inputCommand = scanner.next().charAt(0);

        switch(inputCommand)
        {
                case 'C':
                case 'c':
                    System.out.print("\033[H\033[2J"); 
                    System.out.flush();
                    System.out.println("\rAPI Service is Running and Accepting Requests from y'all!");
                    System.out.println("\rType 'H'at the Service Prompt for Help.");                
                    break;

          case 'X':
          case 'x':
              //X = Shutdown Service and Exit
              System.out.print("\nShutting down Service...");

              //Stop the server gracefully with a 10-second delay
                      server.stop(5);

              System.out.print("Done!\n");
              scanner.close();
              System.exit(0);
              break;

          case 'H':
          case 'h':
              //H = Help
              System.out.println(("*".repeat(40)));
              System.out.println("            API Service Help");
              System.out.println(("*".repeat(40)));
              System.out.println("C - Clear Console Screen");
              System.out.println("V - Toggle Verbose (Silent) Mode. Default is False,");
              System.out.println("H - Help with API Service");
              System.out.println("X - Shutdown Service and return to Server Prompt");
              System.out.println(("-".repeat(80)));
              break;

          default:
              System.out.println("Unknown Command!");
              break;

      }
    }

This indefinite while(1) loop after starting server, allows the user to intervene with a Command to do specific tasks while the Server is doing its thing in parallel in the background. When the user presses "C", we are using ANSI codes to clear out the screen and reprint our Command prompt. When the user enters 'H', we are showing a list of commands supported. When the user presses "X", we are shutting down the Service *gracefully* and exiting the application. In a real life application, this shutdown section is the place to a long list of clean up and logging tasks before shutdown. Finally, we handle cases where the User enters some random character in the default section.

Compile and Run your app. You should see the "command prompt". Enter the commands we made one by one. This is how your screen should look. Lovely Na?

Now let us add code, that will actually "Log" requests+responses in real time. We do that with a custom function which we will add to a new static class called projLib. Add the following class to your code just after the ApiService class in your code.

public static class projLib
    {
        public static int RequestCount=0;
        public static Date LogDate;

        public static void logRequest(HttpExchange xchg)
        {
            String response;
            String URI = xchg.getRequestURI().toString();
            String Method = xchg.getRequestMethod();
            InetSocketAddress remoteAddress = xchg.getRemoteAddress();
            String clientIP = remoteAddress.getAddress().getHostAddress();
            String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());

            response = "\r" + timeStamp;
            response += " ➣ " + clientIP;
            response += " ➣ " + Method;
            response += " ➣ " + URI + "\n";
            RequestCount++;

            if(!verboseMode)
            {
                System.err.print(response);
                System.out.print(" 🖝   ");
            }
        }
    }

We will call projLib.logRequest() whenever we receive a request. Note that we are passing an object of class HttpExchange() as a parameter. This object will contain details of the Request made. We first collect the Date using We use the getRequestURI() method to determine the exact URL sent with the request. We also determine the IP Address using the getRemoteAddress() and getHostAddress() methods in unison. Finally we determine whether the request was a "GET" or a "POST" request - more on this later! Having collected all this information, we nicely format the details and print it out to the screen using the print(). This is also the point where we can add code to format the log data we collected and insert into a Text file or our Database. You get the idea! :) 

To log data, we simply add this single line to call the projLib.logRequest() function in all our Handler functions! That is it!

Update our Handler function by adding one line as shown below

static class ModaniHandler implements HttpHandler 
{
	@Override
	public void handle(HttpExchange xchg) throws IOException {
      String response = "Arrest Modi-Adani NOW!";
      xchg.sendResponseHeaders(200, response.length());

      //OPTIONAL - Print Status on Server Console
      System.out.println("Sent Modani Response");

      projLib.logRequest(xchg);

      OutputStream os = xchg.getResponseBody();
      os.write(response.getBytes());
      os.close();
  }
}

That is it! Every time a request is made it will show a detailed log of the same on the Server Console. Compile and run app!

 


We can later add code to our basic logRequest() function to insert the log data into our database for later analysis of Usage patterns. This can be very useful for planning!

For eg, analysis of the API server logs on my Real Estate Brokers application showed that we received average of just 12 requests/hour during working hours between Monday to Friday.   However there was a noticeable daily peak every morning between 8am and 10am Mountain Time where requests shot up to 200-500 requests/hour. There was some minor activity even on Saturdays and Sundays of between 5/10 requests in the day which showed that SOMEONE atleast was working. Utilisation was ZERO between Sunday evening 4pm to Monday mornings 8am. What did this tell us? It told us how our users were using the system. It gave us a very good idea on when exactly to schedule downtime for Server maintainance and upgradation! So this "Lipstick Laavne" task is actually quite useful!

That is it for today folks! Today we have seen how to handle server tasks gracefully and collect and log request info. Tomorrow, we see how to create and return JSON data back to the user instead of plain text. Please support my product work with a contribution using the link below. 🙏



Comments