SoatDev IT Consulting
SoatDev IT Consulting
  • About us
  • Expertise
  • Services
  • How it works
  • Contact Us
  • News
  • June 16, 2023
  • Rss Fetcher

Bringing UNIX to the Browser: Streaming ‘tail -f’ Output to a Web Interface (Part 2)

Let’s focus on the frontend implementation and finish with an operational end-to-end application

Photo by Geran de Klerk on Unsplash

Introduction

This is the second part of the ‘tail -f’ article. We are going to focus on the frontend implementation this time. So, by the end of this article, we will have an operational end-to-end application.

The original backend implementation can be found here.

Picking a Technology Stack

Any modern web frontend framework should work to perform this task. Feel free to pick the right one for yourself. We’ll be using ReactJS on the frontend, due to its popularity and my familiarity. 🙂

The next part of our stack, a somewhat unassuming player, is the Textarea HTML element. ‘Why use Textarea?’ you might ask. ‘Why not some snazzy, new-fangled UI component?’ Well, here’s the deal: Textarea is simple but effective. Built to handle multiline text input, it’s perfect for mimicking the line-by-line nature of log files. Auto-scrolling? No problem. Textarea’s got that covered, too, making log tracking a breeze.

Of course, it’s not all sunshine and roses. Textarea can bog down when overloaded with too much text, and the styling options are somewhat limited. But given our objective — to mirror the ‘tail -f’ functionality in a web UI — Textarea’s simplicity and built-in features prove to be the winning factors.

Setting Up the Development Environment

I always have an updated ReactJS boilerplate to kickstart new frontend projects swiftly. Feel free to check out my GitHub repository here. If you’re not a ReactJS fan or wish to use a different framework, go ahead! The fundamental work should remain the same or bear a striking resemblance.

Connecting to our WebSocket Backend

Let’s connect the frontend to the backend by establishing a WebSocket connection. It’s important to note the WebSocket URI scheme is either ‘ws’ or ‘wss’ for secure connections, much like ‘HTTP’ and ‘HTTPS’ for traditional HTTP connections. We create a new WebSocket instance and provide our server’s URI.

// app.js
import React from 'react';
import LogDisplay from './components/LogDisplay';

function App() {
return (
<div className="App">
<LogDisplay />
</div>
);
}

export default App;
import React, { useEffect, useRef } from 'react';

function LogDisplay() {
const socketRef = useRef(null);
useEffect(() => {
socketRef.current = new WebSocket('ws://localhost:8000/ws');
socketRef.current.onmessage = (event) => {
const message = event.data;
console.log(message);
// update textarea here with the new message
};
return () => {
socketRef.current.close();
};
}, []);
return <textarea readOnly={true}>{/* Update this area with the received log data */}</textarea>;
}

export default LogDisplay;

We initiated by setting up our central application component and importing the necessary modules. The main application function rendered a LogDisplay component.

At this point, when we test it in the browser and point it to localhost:3000. It would show empty text box with no content inside. If we check our debugger console in the browser, it should show some log messages about what’s been received from the backend. If you are having trouble seeing these log messages, make sure the port and endpoints are aligned with the setup on the server.

If you see a warning that Websocket is closing and reopening on the console, you might be experiencing the known issue where React’s useEffect() hook is called twice. This Stack Overflow answer may help you.

Next, we focused on the LogDisplay component. We utilized React’s hooks to manage our WebSocket connection. We created a ref with useRef to store our WebSocket instance and instantiated our WebSocket inside the useEffect hook. We also set up an event listener for incoming messages.

To ensure the WebSocket connection is closed when the LogDisplay component unmounts, we added a cleanup function in the return of the useEffect hook.

Lastly, we rendered a textarea to display the incoming log data, setting it to readOnly to prevent user modification.

Now, setting up a message event listener for our WebSocket instance is crucial. This is what enables us to receive data from the server. In our case, we’ll receive log updates, which we’ll push to our component’s state. As a result, each new log update received will trigger a state change, and a subsequent component rerender, effectively displaying the new log data in our Textarea.

const ws = new WebSocket('ws://localhost:8080');

ws.onmessage = (event) => {
... // to be implemented
};

Updating the Textarea UI on Messages Received

With our WebSocket connection in place and receiving data, we need to reflect these changes in our UI. We’ll be using React’s state to accomplish this. The received log data will be stored in our component’s state’s ‘logs’ (long) string. (I used an array to store the logs instead, which can archive the same result by using logs.join(‘n’) at the textarea value part)

const [logs, setLogs] = useState('');

Next, we need to ensure that our Textarea is linked to our component’s state so it displays the logs received. We can use React’s powerful two-way data binding to set the value of the Textarea to our state’s ‘logs’ string. Remember, in React, it’s important to ensure that state changes trigger UI updates. This is a fundamental part of its design.

<textarea value={logs} readOnly />

Lastly, to make our UI more user-friendly, we must ensure that our Textarea automatically scrolls to display the latest logs. We can accomplish this by using the scrollTop property of the Textarea. This value should be updated every time new logs are added to the state, which will scroll Textarea to the bottom.

this.textAreaRef.current.scrollTop = this.textAreaRef.current.scrollHeight;

Combining these updates and adding some code cleanups. We have this in our LogDisplay component.

import React, { useEffect, useRef, useState } from 'react';
export default function LogDisplayComponent() {
const [logs, setLogs] = useState('');
let ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket('ws://localhost:8000/ws');
ws.current.onmessage = (event) => {
setLogs(
(prevLogs) => `${prevLogs}n${event.data}`
);
};
ws.current.onclose = () => console.log('ws closed');
ws.current.onerror = () => console.log('ws error');
return () => {
ws.current.close();
};
}, []);
return (
<div>
<textarea value={logs} readOnly style={{ width: '80vw', height: '80vh' }} />
</div>
);
}

We can go ahead and test this on our browser. Looks like this worked. But it feels like it’s incomplete without having a way to ‘clear’ the log. So, let’s add this button to our UI.

import React, { useEffect, useRef, useState } from 'react';
export default function LogDisplayComponent() {
const [logs, setLogs] = useState('');
let ws = useRef(null);
const clearLogs = () => {
setLogs('');
};
useEffect(() => {
ws.current = new WebSocket('ws://localhost:8000/ws');
ws.current.onmessage = (event) => {
setLogs(
(prevLogs) => `${prevLogs}
${event.data}`
);
};
ws.current.onclose = () => console.log('ws closed');
ws.current.onerror = () => console.log('ws error');
return () => {
ws.current.close();
};
}, []);
return (
<div>
<textarea value={logs} readOnly style={{ width: '80vw', height: '80vh', display: 'block' }} />
<button onClick={clearLogs}>Clear</button>
</div>
);
}

Conclusion

In this tutorial, we’ve come a long way. We’ve set up a full-fledged system for tailing file logs on a React web frontend, laid the groundwork with our React setup, and built the Textarea element to display our logs. After all that, we established a WebSocket connection to the backend to receive real-time log updates.

We handled potential connection errors and kept the user experience smooth, even during edge cases. Lastly, we provided a way for users to clear logs, giving them control over their UI.

However, there’s always room for improvement and adaptability. This setup can be enhanced in many ways — adding features such as log filtering or categorizing, letting users customize their UI, or even expanding the setup to handle multiple file logs simultaneously. Our setup is flexible and can be easily adapted to other real-time data display needs beyond file logs. Here’s the full source code on GitHub.

Keep exploring and pushing the limits of what can be done with React and WebSockets!


Bringing UNIX to the Browser: Streaming ‘tail -f’ Output to a Web Interface was originally published in Better Programming on Medium, where people are continuing the conversation by highlighting and responding to this story.

Previous Post
Next Post

Recent Posts

  • Computing the Euler-Mascheroni Constant
  • Golden ratio base numbers
  • Pioneering Apple engineer Bill Atkinson dies at 74
  • Lawyers could face ‘severe’ penalties for fake AI-generated citations, UK court warns
  • At the Bitcoin Conference, the Republicans were for sale

Categories

  • Industry News
  • Programming
  • RSS Fetched Articles
  • Uncategorized

Archives

  • June 2025
  • May 2025
  • April 2025
  • February 2025
  • January 2025
  • December 2024
  • November 2024
  • October 2024
  • September 2024
  • August 2024
  • July 2024
  • June 2024
  • May 2024
  • April 2024
  • March 2024
  • February 2024
  • January 2024
  • December 2023
  • November 2023
  • October 2023
  • September 2023
  • August 2023
  • July 2023
  • June 2023
  • May 2023
  • April 2023

Tap into the power of Microservices, MVC Architecture, Cloud, Containers, UML, and Scrum methodologies to bolster your project planning, execution, and application development processes.

Solutions

  • IT Consultation
  • Agile Transformation
  • Software Development
  • DevOps & CI/CD

Regions Covered

  • Montreal
  • New York
  • Paris
  • Mauritius
  • Abidjan
  • Dakar

Subscribe to Newsletter

Join our monthly newsletter subscribers to get the latest news and insights.

© Copyright 2023. All Rights Reserved by Soatdev IT Consulting Inc.