Pull to refresh

Микроинструменты: централизованное логирование и просмотрщик иконсетов Fontello

Reading time10 min
Views2.7K
В процессе разработки чего-либо веб-разработчик сталкивается с рядом рутинных задач. В сегодняшнем посте я хотел бы поделиться двумя своими микроинструментами, решающими две задачи: централизованное логирование проектов и просмотр собственноручно созданных в сервисе Fontello иконочных шрифтов. Итак, доброго всем здравия и добро пожаловать под кат.



Централизованное логирование проектов

Количество проектов, размещённых на моей площадке исчисляется десятками. Проекты разные — от мала до велика. И большую часть я периодически меняю, отлаживаю и т.д… Дело каждого, конечно, но лично я считаю вывод ошибок отладки на страницы самого проекта моветоном. Тем более, что существует возможность определить в файле htaccess перенаправление вывода ошибок в отдельный файл. Разумеется, держать лог проекта в его же папке не всегда удобно, да и вообще логично собирать все логи с одной площадки в одно и то же место. Но мало их собрать. Нужен ещё и удобный инструмент, дабы управлять логами из браузера, ибо удобно.

Собственно сам скрипт довольно прост и содержит в себе всё необходимое, не имея внешних файлов:
<?php
/* OPTIONS */
  define("PTPL_DATE",'/^\[(\d+)-(\w+)-(\d+)(\s+)(\d+):(\d+):(\d+)\]/si');
  define("PRPL_DATE",'<b class="badge">\1-\2-\3 \5:\6:\7</b>');
  
/* DATA */
  $_ = <<<DATA
<!DOCTYPE html>
<html><head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title>LOG viewer</title>
  <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/bootstrap/2.3.2/css/bootstrap.min.css" />
  <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Cuprum&subset=latin" />
  <link rel="stylesheet" type="text/css" href="http://netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css" />
  <link rel="stylesheet" type="text/css" href="index.php?sect=css" />
  <link rel="icon" type="image/png" href="index.php?sect=icon" />
  <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
  <script type="text/javascript" src="index.php?sect=js"></script>
</head><body><div class="container-fluid">
<div class="row-fluid">
  <div class="toolbox span3">
    <a id="el-menu" class="btn btn-info fa fa-refresh" href="index.php?sect=menu" data-target="side">Refresh</a>
  </div>
  <div class="toolbox span9">
    <a id="el-content" class="btn btn-info fa fa-refresh" href="index.php?sect=log" data-target="content">Refresh</a>
    <a class="btn btn-danger fa fa-times-circle" href="index.php?sect=log&act=clear" data-target="content">Clear</a>
  </div>
</div>
<div class="row-fluid" id="el-root">
  <div class="side span3"><div></div></div>
  <div class="content span9"><div></div></div>
</div>
</div></body></html>
DATA;
  define("APP_PAGE",$_);
  define("APP_EMPTY",'<div class="logitem info"><span class="fa fa-info-circle"></span> Log is empty!'."</div>\n\n");
  
  $_ = <<<JS
(function(\$){
  \$.fn.HTTPError = function(E){
    var I = parseInt(E.status);
    \$(this).html('').append(\$('<div>').addClass('logitem error').append(
      \$('<span>').addClass('fa fa-'+((I == 401 || I == 403)?'lock':'times-circle'))
    ).append(
      \$('<span>').text(' '+E.statusText)
    ));
  }
  
  \$.fn.logClick = function(T,E,F){
    E = E || false;
    if (E) E.removeClass('active error').addClass('loading').siblings().removeClass('active error loading');
    \$.get(\$(this).attr('href')).success(function(D){
      \$('#el-root > div.'+T+':first > div:first').html(D);
      if (E) E.removeClass('loading').addClass('active');
      if (typeof F == 'function') F();
    }).error(function(R){ \$('#el-root > div.'+T+':first > div:first').HTTPError(R); if (E) E.removeClass('loading').addClass('error'); });
    return false;
  }
})(jQuery);

\$(function(){
  \$('#el-root > div.side:first').on('click','li > a.item',function(){ return \$(this).logClick('content',\$(this).parent()); });
  \$('div.toolbox').on('click','a.btn',function(){ return \$(this).logClick(\$(this).attr('data-target')); });
  \$('#el-root > div.side:first').on('click','li > a.fa-times',function(){
    return \$(this).logClick('content',\$(this).parent(),function(){
      \$('#el-menu').click();
      \$('#el-content').click();
    });
  });
  \$('#el-menu').click();
  \$('#el-content').click();
});
JS;
  define("APP_JS",$_);
  
  $_ = <<<CSS
html,body,div.container-fluid { height: 100%; }
#el-root { height: 75%; }
div.span3 { width: 21.769%; padding: 1%; }
div.span9 { width: 72.359%; padding: 1%; }
a.btn.fa { padding: 4px 8px; font: 16px Cuprum,Arial,sans-serif; }
a.btn.fa:before { margin-right: 4px; font: 16px FontAwesome; }
#el-root > div.side,
#el-root > div.content { height: 100%; border: 1px solid silver; border-radius: 5px; }
#el-root > div.side > div,
#el-root > div.content > div { height: 100%; overflow-x: hidden; overflow-y: auto; }
#el-root > div.side ul { list-style-type: none; margin: 0; padding: 0; }
#el-root > div.side ul li { display: block; padding: 4px; color: #358; font: 16px Cuprum,Arial,sans-serif; border-radius: 4px; position: relative; }
#el-root > div.side ul li.loading { background: #707788; color: #d0dddf; }
#el-root > div.side ul li.error { background: #510; color: #d0dddf; }
#el-root > div.side ul li.active { background: #358; color: white; }
#el-root > div.side ul li > a.fa-times { display: none; position: absolute; right: 4px; text-decoration: none; color: red; }
#el-root > div.side ul li:hover > a.fa-times { display: inline; }
#el-root > div.side ul li.active > a.fa-times,
#el-root > div.side ul li.error > a.fa-times,
#el-root > div.side ul li.active > a.item,
#el-root > div.side ul li.error > a.item { color: white; text-decoration: none; }
#el-root > div.side ul li.loading > a.fa-times { display: none; }
#el-root > div.side ul li.loading > a.item { color: #d0dddf; text-decoration: none; }
#el-root > div.side ul li:before { margin-right: 4px; font: 16px FontAwesome; }
div.logitem { padding: 4px; border-radius: 4px; }
div.logitem.info,
div.logitem.error { font: 20px Cuprum,Arial,sans-serif; padding: 24px; }
div.logitem.info { background: #e0eeff; }
div.logitem.info span.fa { color: royalblue; }
div.logitem.error { background: #ffe0cc; }
div.logitem.error span.fa { color: red; }
CSS;
  define("APP_CSS",$_);
  
  $_= 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2Fy'
    . 'ZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAfJJREFUeNqMUj1rG0EQnbtboYhESFfY'
    . 'Jp8ESYFgo8qNazltuvyI/InYYFKkDoG4NqjLHwiGgCGFK3N9jCVIYYyRzrY+zljS'
    . 'Ku+NtdIanJCFudnd2ffmzcwF77a2hCsIgrdwj+Xf64e19thOJjKdTvXCuA38k73t'
    . '7d0bBEc0a2WCmIsnSSJ7+/vvmQv2yzGGZJtZYAkajdRurq8lGwyk3+9Lr9dT/21n'
    . 'ZxfvNoF75QjMBODZCkgd4puLooXo8VjGIO52u/Kl2ZTG6urXz83mB0Q+KoFdEIQE'
    . '/zw6UtnlclmGw6G8rtWUpNFo6H2xWJTvSZLOFYx9BSCoAcBS+DiOY3mQy91RoluU'
    . 'uygBAb+EVqul2TudjpKczBpZKpUkyzKp1+sCzL0EWkK1Wp1nd0qc8RzhDTDhogSP'
    . 'gLTtdnsOdEroeWZPnq6soJr7CbQHToHfB3+fC0MSeCVg5r4C1wOOjUA/ez6fl5dQ'
    . 'AMxfSoCCSqWiB2Yj8BmaRyJLVbBAB+KVcHl6Kg+Xlu5Mwcll1hdQgPaKzEbIRrsS'
    . 'BufnYo4PDuT5xoYU4tjw8s36+m0P2Hmc6cPbGSkJkfjFTZam8vvwUGMFbJYv0zTv'
    . 'dRPvQ80WwdMMfm9jjETwfEsMscxagC1fnZ1dPFpb+yT/sWyWXRDDdvwRYAB4+UxU'
    . 'y0hc0wAAAABJRU5ErkJggg==';
  define("APP_ICON",$_);

/* LIB */  
  function _is_log($name) { return !in_array($name,array('.','..','cgi-bin','.htaccess','.htpasswd','index.php')); }
  
/* MAIN */
  session_start();
  $out = '';
  
  if (isset($_GET['file'])) {
    if (ctype_alnum(str_replace(array('.','_','-'),'',$_GET['file']))) {
      if (is_file($_GET['file']) && _is_log($_GET['file'])) {
        $_SESSION['currentlog'] = $_GET['file'];
      } else { header('HTTP/1.0 404 Not found'); }
    } else { header('HTTP/1.0 400 Bad request'); }
  } else {
    if (!isset($_SESSION['currentlog'])) {
      $logs = scandir('.'); $fnd = '';
      foreach ($logs as $f) if (_is_log($f) && ($fnd=='')) $fnd = $f;
      if ($fnd != '') $_SESSION['currentlog'] = $fnd;
    }
  }
  
  $act  = isset($_GET['act'])?(in_array($_GET['act'],array('clear','del'))?$_GET['act']:'view'):'view';
  $sect = 'index';
  if (isset($_GET['sect'])) $sect = in_array($_GET['sect'],array('menu','icon','js','css','derr'))?$_GET['sect']:'log';
  
  if (isset($_SESSION['currentlog']) && isset($_SERVER['PHP_AUTH_USER'])) {
    switch($sect){
      case 'derr': header('HTTP/1.0 '.intval($_GET['e']).' debug error'); exit(); break;
      case 'icon':
        header('Content-Type: image/png');
        $img = imagecreatefromstring(base64_decode(APP_ICON));
        imagesavealpha($img,true);
        imagepng($img); exit();
      case 'css' : header('Content-Type: text/css; charset=utf-8'); echo APP_CSS; exit();
      case 'js'  : header('Content-Type: text/javascript; charset=utf-8'); echo APP_JS; exit();
      case 'log' :
        $out = '';
        switch ($act) {
          case 'clear': file_put_contents($_SESSION['currentlog'],''); $out = APP_EMPTY; break;
          case 'del'  : unlink($_SESSION['currentlog']); unset($_SESSION['currentlog']); break;
          default     :
            $log = file($_SESSION['currentlog']);
            foreach($log as $str) $out.= '<div class="logitem">'.preg_replace(PTPL_DATE,PRPL_DATE,$str)."</div>\n\n";
            if ($out == '') $out = APP_EMPTY;
          break;
        }
      break;
      case 'menu':
        $logs = scandir('.');
        $out .= '<ul>';
        foreach ($logs as $number => $val) if (_is_log($val))
          $out.= '<li class="fa fa-file-text-o'.($val==$_SESSION['currentlog']?' active':'').'">'
              .  '<a class="item" href="index.php?file='.$val.'&sect=log">'."$val</a>"
              .  '<a class="fa fa-times" href="index.php?file='.$val.'&sect=log&act=del"></a>'
              .  "</li>\n";
        $out .= "</ul>\n";
      break;
      default: $out = APP_PAGE; break;
    }    
    header("Content-Type: text/html; charset=utf-8");
    echo $out;
  }
?>

В папке с логами создаём скрипт index.php, копируем туда написанный выше код, защищаем папку базовой HTTP-аутенификацией (при помощи htpasswd).

В .htaccess логируемого проекта добавляем строки:
php_value log_errors "on"
php_value log_errors_max_len "1024"
php_value error_log "/path/to/your/log/file"

И, собственно, всё. При отладке проекта заходим в нашу папку с логами, авторизуемся и выбираем нужный нам лог. Логи можно очищать посредством соответствующей кнопки или удалять вовсе. Все необходимые для работы интерфейса скрипта библиотеки (bootstrap, jQuery) грузятся со своих официальных CDN. Интерфейс на английском, хотя вряд ли это проблема.

Просмотрщик иконсетов Fontello

В процессе разработки своего фреймворка я столкнулся с необходимостью составить свой иконочный шрифт, поскольку популярный FontAwesome не удовлетворял ряду критериев, в частности отсутствовали некоторые иконки, а часть иконок была, мягко говоря, не сильно хорошо отрисована. Шрифт был составлен, однако, я столкнулся с довольно тривиальной рутинной задачей: надо было постоянно держать в браузере вкладку с составленным шрифтом, или запоминать все коды и алиасы, или же постоянно подсматривать в CSS-файл. Все три варианта были весьма неудобны. Посему я написал простой скрипт, создающий на основе сгенерированного CSS-файла предпросмотр шрифта.

Код предельно прост и самодостаточен:

<?php
/* SETTINGS */
  define('fontPrefix',''); // Здесь прописываем префикс шрифта
  define('fontFile',''); // Здесь прописываем путь к файлу шрифта
  
/* DATA */
  define('fontRegExp','/'.fontPrefix.'([\w\-]+):before(\s*){(\s*)content(\s*):(\s*)([\"'."\'".'])(\S)(\w+)([\"'."\'".'])(\s*);(\s*)}/si');
  define('fontFTitle','/^(.*)\@font-face(\s*){(\s*)font-family(\s*):(\s*)([\"'."\'".'])(\w+)([\"'."\'".'])(.*)$/si');
  
/* MAIN SECTION */
  $fontdata = array(); $a = array();
  $fontfile = file_get_contents(fontFile);
  if (preg_match_all(fontRegExp,$fontfile,$a,PREG_PATTERN_ORDER)) {
    foreach ($a[1] as $k => $v) $fontdata[$v] = str_pad($a[8][$k],4,'0',STR_PAD_LEFT);
  }
?>
<!DOCTYPE html>
<html><head>
  <title>Font table for font «<?php echo preg_replace(fontFTitle,'\7',$fontfile); ?>»</title>
  <link rel="stylesheet" type="text/css" href="http://fonts.googleapis.com/css?family=Cuprum&subset=latin" />
  <link rel="stylesheet" type="text/css" href="<?php echo fontFile; ?>" />
  <style type="text/css">
    div.font-table-wrapper { padding: 4px 0; }
    ul.font-table li {
      display: block; float: left; position: relative; width: 200px; padding: 4px 40px;
      cursor: pointer; *cursor: hand;
      border-right: 1px dotted silver;
    }
    ul.font-table li.f { border-left: 1px dotted silver; }
    ul.font-table li .icon {
      position: absolute; left: 8px; top: 2px; display: block; width: 24px; height: 22px; padding-top: 2px;
      font-size: 14px; color: #237; border-radius: 3px;
      background: #cdf; text-align: center;
    }
    ul.font-table li .class { font: 15px Cuprum,Arial,sans-serif; color: #68a; }
    ul.font-table li .code, ul.font-table li .letter { display: block; position: absolute; top: 7px; text-align: center; font: bold 14px monospace; }
    ul.font-table li .code { right: 16px; color: #284; }
    ul.font-table li .letter {
      background: #c0ffc7; width: 24px; height: 20px; top: 2px; padding-top: 4px; border-radius: 3px;
      right: 54px; color: #697;
    }
  </style>
</head><body><div class="container-fluid"><div class="row-fluid"><div class="span12 font-table-wrapper"><ul class="font-table">
<?php
  $c = 0;
  foreach ($fontdata as $k => $v) {
    $_ = '<li'.($c==0?' class="f"':'').'><span class="icon '.fontPrefix.$k.'"></span><span class="class">'.fontPrefix.$k.'</span>'
       . '<span class="letter">&#x'.$v.';</span><span class="code">'.$v.'</span>'."</li>\n";
    echo $_;
    $c = $c==3?0:($c+1);
  }
?>
</ul><br style="clear: both"/></div></div></div></body></html>


В качестве одного из шрифтов используется Cuprum. Это связано с необходимостью для пущей компактности использовать condensed-фактуру. Настроек у скрипта только две — префикс и путь к файлу. У меня скрипт располагался в папке шрифта, а файл стилей назывался index.css. Собственно, это всё, что требуется для работы скрипта.

На сим всё, спасибо за внимание к моим велосипедам.
Tags:
Hubs:
Total votes 7: ↑4 and ↓3+1
Comments2

Articles