Monday, 14 October 2013

Minecraft Server Log Web Interface

Introduction

If you run a Minecraft server in Linux then you'll probably like to keep track of what's going on in the server log. I found that it was a real pain having to remote shell into the server all the time and if it was available via a web browser I could keep track of it more conveniently, or by using my phone or tablet.

I decided that I would use PHP (even though PERL is generally my scripting weapon of choice) and once I'd found the correct commands and syntax it came together quite quickly. If you fancy giving it a try you just need a web server (such as Apache) and of course PHP running on your server.

How it works

It started out being a simple process of opening the server.log file and outputting each line as pre-formatted html.

Something like this..

<html>
<head><title>Minecraft Log</title></head>
<body><pre>
<?php
$filepath = "/opt/minecraft/server.log";
$file = file($filepath);
foreach($file as $line) {
    echo $line."<br/>\n";
}
?>
</pre></body>
</html>

This was great at first, but after we'd been running for a few weeks the server.log file gets too big and the web page gets slower and slower to load. What we needed was a way of only showing the current days log entries, and maybe a date selector so you can reference older stuff.

So I created version two with the following enhancements:-
  • Default to showing today's log entries.
  • A date selector is added to the top of the web page which sends the new value if altered.
  • If page requested with a date parameter then use this instead of today's date.
  • If no date has been passed then run a javascript function to scroll the log section to the bottom.
There's a nice feature in the log where text is coloured. This would typically be for chat or system warnings, but as it stood they left messy control codes littered about the log. Rather than remove these I decided to replace them and style the text to match these colours.

This enhancement required the following:-
  • Create a series of regular expression replaces to match the control codes.
  • Each colour code instance is replaced by a span tag which corresponds to a set of pre-defined styles (the css for these is defined in the head section).
  • Put log into a scrollable div.
Here's the completed code..

<!DOCTYPE html>
<html>
<head>
<title>Minecraft Log</title>
<style>
    #log{
        height:400px;
        overflow-y: scroll;
        margin: 5px 0 0 1px;
        border: 1px solid;
    }
    .green{color: green;}
    .red{color: red;}
    .purple{color: purple;}
    .black{color: black;}
    .blue{color: darkblue;}
    .gold{color: gold;}
    .cyan{color: darkcyan;}
    .aqua{color: cadetblue;}
    .gray{color: #888;}
    .bold{ font-weight:bold;}
</style>
<script>
    function scroll(){
    //scroll div to bottom
    var objDiv = document.getElementById("log");
    objDiv.scrollTop = objDiv.scrollHeight;
    }
</script>
</head>
<?php
$pass = htmlspecialchars($_POST["date"]);
if (!$pass){
    echo "<body onLoad='scroll()'>";
}
else {
    echo "<body>";
}

$filepath = "/opt/minecraft/server.log";
$file = file($filepath);
$dates = array();
$last = "";


/* Get List Of Dates */
foreach($file as $line) {
    $date = substr($line, 0, 10);
    if ($date != $last){
        if(preg_match('/\d{4}\-\d{2}\-\d{2}/',$date)) {
            array_push($dates, substr($line, 0, 10));
            $last = $date;
        }
    }
}

/* If Date Provided Use This */
if(preg_match('/\d{4}\-\d{2}\-\d{2}/',$pass)) {
    $last = $pass;
}

/* Add Form Element and Date Selector */
echo "<form method='post'>Select date: <select name='date' onchange='this.form.submit()'>\n";
foreach($dates as $value){
    $select = "";
    if ($value == $last){ $select = " selected='selected'"; }
    echo "<option value='".$value."'".$select.">".$value."</option>\n";
}
echo "</select></form>\n";

/* Output Log For Required Date */
echo "<div id='log'>";
foreach($file as $line) {
    /* Remap Problem Characters */
    $line = preg_replace("/</","&lt;",$line);
    $line = preg_replace("/>/","</span>&gt;",$line);
    $date = substr($line, 0, 10);
    if ($date == $last) {
        /* Remove Unrequired Formatting Codes */
        $line = str_replace("[m","",$line);
        $line = str_replace("[21m","",$line);
        $line = str_replace("[3m","",$line);
        /* Split Log Line Into Sections to Using First Formatting Code Style */
        $segarray = preg_split( '/(\[0|\[m)/', $line );
        for ($i = 1; $i < count($segarray); ++$i){
            /* Do Replace to Add Styled Spans */
            if (preg_match('/;\d{2};\d+m/', $segarray[$i])) {
                $segarray[$i] = preg_replace("/;30/","<span class='black",$segarray[$i]);
                $segarray[$i] = preg_replace("/;31/","<span class='red",$segarray[$i]);
                $segarray[$i] = preg_replace("/;32/","<span class='green",$segarray[$i]);
                $segarray[$i] = preg_replace("/;33/","<span class='gold",$segarray[$i]);
                $segarray[$i] = preg_replace("/;34/","<span class='blue",$segarray[$i]);
            $segarray[$i] = preg_replace("/;35/","<span class='purple",$segarray[$i]);
                $segarray[$i] = preg_replace("/;36/","<span class='aqua",$segarray[$i]);
                $segarray[$i] = preg_replace("/;37/","<span class='gray",$segarray[$i]);
                $segarray[$i] = preg_replace("/;22m/","'>",$segarray[$i]);
                $segarray[$i] = preg_replace("/;1m/"," bold'>",$segarray[$i]);
                $segarray[$i] = $segarray[$i]."</span>";
            }
        }
        /* Rejoin Then Split Log Line Using Second Formatting Code Style */
        $line = join("",$segarray);
        $segarray = preg_split( '/§/', $line );
        for ($i = 1; $i < count($segarray); ++$i){
            /* Do Replace to Add Styled Spans */
            $segarray[$i] = preg_replace("/^0/","<span class='black'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^1/","<span class='blue'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^2/","<span class='green'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^3/","<span class='aqua'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^4/","<span class='red'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^5/","<span class='purple'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^6/","<span class='gold'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^7/","<span class='gray'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^8/","<span class='gray'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^9/","<span class='blue'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^a/","<span class='green'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^b/","<span class='aqua'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^c/","<span class='red'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^d/","<span class='purple'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^e/","<span class='gold'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^f/","<span class='black'>",$segarray[$i]);
            $segarray[$i] = preg_replace("/^r/","<span class='black'>",$segarray[$i]);
            $segarray[$i] = $segarray[$i]."</span>";
        }
        /* Rejoin and Output to Webpage */
        $line = join("",$segarray);
        echo $line."<br/>\n";
    }
}

echo "</div>";
?>
</body>
</html>

nb. Notice the log must now be read twice!

Installation Instructions

Copy the code into a file called index.php and drop it into a directory accessible to your web server and ensure you grant the file execution rights.

Alter the $filepath = "/opt/minecraft/server.log"; line so that it points to your server log and ensure the web server has read rights to this file.

How to Use

Just open the PHP code in your browser and the a page should open showing today's log entries, scrolled to the bottom of the log.

If you then select an earlier date the page will refresh and show this dates entries without the auto-scrolling.

13 comments:

  1. Wooo, I love that code, so, would be very difficult make a filter? so if I only want to see what the players write in the chat only show that, or only the commands executed by the players (like: nobody issued server command: /plotme home), or the both things without the server messages (like: [WARNING] Tomas124589 moved wrongly! or [INFO] miguel232[/62.210.188.222:39825] logged in with entity id 38016903 at ([world] -15.5, 66.0, 1.5) ) I will use your code but if you implemet that it should be perfect xD, please reply me by a comment or by email (mac.fresno@hotmail.com) I'm very interesting in your code, that it's so useful.

    ReplyDelete
  2. I'm glad you found it useful, and your idea about implementing a filter is a very good idea.

    In a simple way you could easily have a section of regular expression matches that check for things like system messages and filter them out. You'd want to add a radio button to the web page to switch it on an off.

    I hadn't planned on changing it, but you certainly have me thinking about it. If you know enough PHP then certainly try it, but if you struggle I'm sure I can help you out.

    ReplyDelete
  3. I am currently making me a website to access all of my servers logs and password-protect with sessions, but I'm still learning php and I do not know how to make the filter xD, but I will try it tomorrow, I'll tell you my progress xD.

    PD: I've just know your web and you have some very interesting articles, good job ;)

    ReplyDelete
  4. Yeah, I've gotten all the logs protect by password, I will try to implement the filter, another idea I've had is that the user can resize the area of the log, like the comment box of this blog (the box where you write), you can make it bigger or smaller, it should be easy to do, I'll try.

    ReplyDelete
    Replies
    1. You'll want to keep the log in a div or you'll lose the text colours if you have it displayed in a textarea.

      If you set the html height to be 100% and the body height to be 100% using CSS (you might also want to set the overflow to hidden to stop a second scroll bar from showing), then you can set your log div to be something like 90% and it'll roughly size to the window.

      Delete
    2. OK I've altered my version to fit the page better by altering the CSS at the start of the code..

      html,body{height:100%;}
      body{overflow:hidden;}
      #log{
      height:90%;
      overflow-y:scroll;
      margin:5px 0 0 1px;
      border:1px splid;
      }

      Then in the main loop I've added some filters....

      foreach($file as $line) {
      if (
      !(preg_match('/\[INFO\} Connection reset/', $line) OR
      preg_match('/lost connection$/', $line)
      ){
      $line = ....... continue with existing code.....

      } // don't forget to add this closing brace at the end of the block inside the foreach loop!

      This should remove the following log entries:
      [INFO] Connection reset
      [INFO] /80.45.124.45 lost connection

      Delete
    3. Not sure if you understand regular expressions so I'll explain what I've done...

      I'm using the preg_match function that runs a regular expression match which is enclosed in forward slashes eg '//'. If the pattern is found anywhere in the target string eg $line then true is returned.

      So the first search is for '[INFO] Connection reset',.. we enclose this within slashes and escape the [ and ] characters with back slashes so that it doesn't mess up the regular expression.

      The second search looks for 'lost connection' at the end of the line, so again we enclose it in slashes and add the $ character to tell it to check on the end of the line only.

      You should be able to add as many additional lines to this block of code by adding an additional OR clause eg..

      if (
      !(preg_match('/\[INFO\} Connection reset/', $line) OR
      preg_match('/lost connection$/', $line) OR
      preg_match('/moved wrongly\!$/
      ){

      (nb. Here I had to escape the ! character)

      Hopefully that should get you working :-)

      Delete
    4. Hey that it's so usefull, I were so bussy but I will return to work soon. Thank for all :D

      Delete
    5. I am running the default minecraft server but the latest.log only contains the time. I do not have the server.log ? Hence is this weblog viewer only for teh bitbucket?

      Chrs

      Delete
    6. On later versions of the vanilla version they changed the logging.

      Instead of one big log, they rotate the logs at least daily so date no longer features in the log file.

      We've found that versions like Tekkit and Hexxit still use the old format. Annoying that they changed it.

      Delete
    7. Good idea.
      I just addapted the code for the latest version of the MineCraft server (1.8.3 at the time) and played a bit with it. Use it as you like you can find it here:

      http://mc.gids.nl/mclogv.zip

      Chrs..

      Delete
    8. Thanks for your comment Jeepee, would love to see your code as I've just updated my server to 1.8.3 and now my log viewer doesn't work.

      But, that link gives me a permission error.

      Delete