0

I am trying to display the output of /var/log/messages or similar (../secure for instance) in a webpage that I access through a webserver on the same host.

Should I use bash to tail -f >> the messages file to a new output file and display that text file in the html page, or is there a better way to do this?

Thanks! idiglivemusic

5 Answers 5

2

If you're looking for a way to display actual file contents online without the need to reload the page, then you should setup a WebSocket server.

You can build a WebSocket server using a framework such as phpDaemon, ReactPHP, Ratchet, icicle, or implement your own server with the help of PHP extensions wrapping asynchronous libraries: event, ev, or similar.

I've chosen a random framework from the list above: Ratchet. Ratchet is based on ReactPHP. ReactPHP chooses a backend for the event loop from the following list: - libevent extension, - libev extension, - event extension, - or an internal class based on the built-in stream_select() function.

As a maintainer of the event extension, I've chosen event.

I've written a "quick" example just to give you idea of how it might be implemented. You'll most likely have to work out your own version, maybe using different tools. But the code should give you an impulse.

src/MyApp/Server.php

<?php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Server implements MessageComponentInterface {
  protected $clients;

  public function __construct() {
    $this->clients = new \SplObjectStorage;
  }

  public function onOpen(ConnectionInterface $conn) {
    $this->clients->attach($conn);
    echo "New connection! ({$conn->resourceId})\n";
  }

  public function onMessage(ConnectionInterface $from, $msg) {
    $numRecv = count($this->clients) - 1;
    printf("Connection %d sending '%s' to %d other connection%s\n",
      $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');

    foreach ($this->clients as $client) {
      if ($from !== $client) {
        $client->send($msg);
      }
    }
  }

  public function onClose(ConnectionInterface $conn) {
    $this->clients->detach($conn);
    echo "Connection {$conn->resourceId} has disconnected\n";
  }

  public function onError(ConnectionInterface $conn, \Exception $e) {
    echo "An error has occurred: {$e->getMessage()}\n";
    $conn->close();
  }

  public function broadcast($msg) {
    foreach ($this->clients as $client) {
      $client->send($msg);
    }
  }
}

server.php

<?php
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Server;

require __DIR__ . '/vendor/autoload.php';

$server = IoServer::factory(
  new HttpServer(
    new WsServer(
      $my_app_server = new Server()
    )
  ),
  9989
);

$loop = $server->loop;
$filename = '/var/log/messages';

$loop->addPeriodicTimer(5, function ()
  use ($filename, $my_app_server)
  {
    static $stat_info;

    if ($stat_info == null) {
      clearstatcache(true, $filename);
      $stat_info = stat($filename);
    }

    clearstatcache(true, $filename);
    $st = stat($filename);

    $size_diff = $st['size'] - $stat_info['size'];
    echo "Diff = $size_diff bytes\n";

    if ($size_diff > 0) {
      $offset = $stat_info['size'];
      $bytes = $size_diff;
    } elseif ($size_diff < 0) {
      // The file is likely truncated by `logrotate` or similar utility
      $offset = 0;
      $bytes = $st['size'];
    } else {
      $bytes = 0;
    }

    $stat_info = $st;

    if ($bytes) {
      if (! $fp = fopen($filename, 'r')) {
        fprintf(STDERR, "Failed to open file $filename\n");
        return;
      }
      if ($offset > 0) {
        fseek($fp, $offset);
      }
      if ($msg = fread($fp, $bytes)) {
        $my_app_server->broadcast($msg);
      }
      fclose($fp);
    }
  }
);

$server->run();

test.html

<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <title>Test</title>
</head>
<body>
  <script>
  var conn = new WebSocket('ws://localhost:9989');
  conn.onopen = function(e) {
    console.log("Connection established!");
  };
  conn.onmessage = function(e) {
    console.log("Msg from server", e.data);
  };
  </script>
</body>
</html>

I'll skip the steps required to setup a basic test environment using Composer. Assuming you have successfully configured the test environment for the files above, you'll be able to run the server with the following command:

php server.php

Check, if the user has permissions to read /var/log/messages. On my system only root can read the file. So you might need to run the above-mentioned command with sudo(root permissions).

Now you can open test.html in a browser and look at the console output. Then trigger some event which is normally logged to the messages file. For instance, you can invoke sudo with a wrong password. The server should detect the changes within interval of 5 seconds, then send it to the WebSocket clients.

Sign up to request clarification or add additional context in comments.

4 Comments

This is looks like a complete solution for the problem :D
Ruslan, thanks for your response. Very thorough! Can you please clarify where the src/MyApp/Server.php file goes? Do you mean for it to go in /usr/src/MyApp/Server.php??? (!!!)
@iDigLiveMusic, it's just a PSR-0 within your project directory which can by anywhere on the file system. See Ratchet's "hello world" example
Great I appreciate all the help. While this involved more than what I looking for, I still upvoted as I am sure this will probably end up helping some unsuspecting reader :)
1

If you're using tail -f, that means that you'll be continuously getting data from the file while it grows as the command runs. You can use cat or tail -n. Also, of course, you can access files directly by creating a symbolic or hard link to them (ln source-file link-file, ln -s source-file link-file) - but make sure, that your web-server has enough rights to access them.

1 Comment

Thanks for the fast response Sergey! I ended up with a solution that avoids symbolic links.
0

Make sure that your html-server has rights to access the page and read the page (with something like cat, tail, grep). In <html> put the output between <pre> and </pre>.

Comments

0

Method 1

In one of your base directories, create a symbolic link

ln -s /var/log/messages messages

If the directory belonged to say, test.web, access the log with unmesh http://test.web/messages

Method 2

If you're looking for a php script then, first create the link as mentioned in method 1. Then, create a new file, say, readlog.php in the base directory of test.web with the below content :

<?php
readfile(“$DOCUMENT_ROOT/messages”);
?>

Access the readlog.php like :

http://test.web/readlog.php

Requirement:

Read access should be enabled for all users for /var/log/messages.

Note:

Setting read option for /var/log/messages for the whole world is NOT a good idea.

1 Comment

Thanks for the response! Because of the obvious security concerns (which you've pointed out) I might pass on this one, but it technically addresses my issue albeit using php instead of just html
-1
<!DOCTYPE html>
<html>
 <head>
  <title>toyLogs</title>
 </head>
 <body>
  <div><p><?php include('/var/www/html/accesslog.txt'); ?></p></div>
 </body>
</html>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.