Pull to refresh

Rails 4 Engines. Разработка gem'а через mountable engine — читаем логи сервера

Reading time6 min
Views9.9K

Так уж случилось, что возникло непреодолимое желание написать свой Rails gem. Во-первых, академический интерес — такого еще не делал, во-вторых, назрела проблема, решение которой важно лично для меня и которое хотелось бы использовать в нескольких своих проектах.

На Хабре уже были статьи про создание gem'ов (раз два три)

Но на их основе создать полноценный gem нельзя — они сильно устарели и, как правило, представляют собой перевод скупой официальной документации. А главное, они в большей части описывают создание Readme и License файлов, а собственно функционал gem'a сводится к Hello World.

Проблема


Не знаю, как у кого, а вот у меня регулярно бывает ситуация — прикрутил новую фичу локально, проверил, вроде работает. Запускаешь cap deploy, смотришь на сервер, а там
«Sorry, but something went wrong.
If you are the application owner check the logs for more information.»

Ну а дальше — ssh к серверу, cd к папке приложения и раскопка логов. Что бы ни говорили поклонники vim и emacs, но пытаться найти что-то в логе с их помощью — то еще занятие. Проще уж запустить tailf и пытаться найти руками. Есть еще rmate, но у меня он как-то не прижился.

Идея


Написать gem, который будет выводить результаты команды tail в браузер по заданному пути. Желательно, чтобы была возможность смотреть все .log файлы в папке log/

Сразу покажу, что получилось в итоге:


Все, что нужно, это прописать в Gemfile
gem 'tail'

установить gem
bundle install

и смонтировать его (config/routes.rb) в нужную точку приложения, например
mount Tail::Engine, at: "/tail"

После этого на вашем сервере
появится вот такая страница:

Локально работает точно так же.

Если в приложении используется Devise, то нужно будет сначала ввести логин и пароль.

Как делать


Обычно для таких случаев используют sinatra-based gem'ы (например)
Лично я для своего gem'a не вижу в этом смысла, поскольку использую Rails и нет необходимости в синатре.

Для начала имеет смысл написать собственно приложение, которое впоследствии будет превращено в отдельный gem.
В моем случае оно будет состоять из одного конроллера и одного экшена:

  class LogsController < ApplicationController
    before_filter :authenticate_user! if defined? Devise

    def index
      @web_logger ||= Log.instance
      @web_logger.n = params[:n]
      log_file_name = params[:file_name] || "#{Rails.env}.log"
      @files = @web_logger.tail(log_file_name)
    end
  end

Все просто, используем один Singleton класс, который выдает заданное количество строк из заданного .log файла. Сам класс Log интереса не представляет. Разве что сам способ получения лога:

@files.include?(file_name) ? `tail -n #{@n} log/#{file_name}`.lines : []

Ruby нативно позволяет выполнить команды ОС, просто заключив ее в вот эти (хрен его знает, как они правильно называются) символы: `tail -n 40 production.log` — вернет последние 40 строк из файла.

Полученные строки помещаются в таблицу, слегка облагораживаются CSS и выводятся на экран.

Одна строчка JavaScript (известный фреймворк — Vanilla.js) прокручивает экран вниз, к последней строке:

'window.scrollTo(0, document.body.scrollHeight);'


Делаем gem из приложения.


Статьи на Хабре и уважаемый Ryan Bates описывали создание gem'a с помощью команды bundle gem. Последнее видео датируется ноябрем 2011 года.
Сейчас же создение Rails gem'ов рекомендуется делать через engines.

Engine — это по сути еще одно Rails приложение, которое будет запущено вместе с исходным и использующее (взаимно) его ресурсы. Сиамские близнецы, в общем. Или имплантанты, как кому больше нравится. В приложении-родителе указывается точка подключения нового engine/plugin/gem и далее по этому адресу доступна вся функциональность нового приложения. Объяснение примитивное, на пальцах, кому интересны подробности — я указал ссылки внизу.

Таким образом достаточно удобно реализовывать всяческие админки, gem'ы статистики, наблюдения за активностью и пр. В общем все то, что, с одной стороны, относительно независимо и не так тесно переплетается с основным приложением, а с другой стороны, задействует полную функциональность Rails — модели, контроллеры, представления и т.п.
В противном случае надо будет серьезно попотеть с порядком инициализации приложений, миграциями, разграничением доступа, безопасностью и т.п. Головняк тот еще, но нет ничего невозможного. Кому интересно — можно начать с вот этой статьи

Для gem'ов, работающих только с ограниченной функциональностью Rails (например создающих новый хелпер или фильтр контроллера) — лучше использовать плагины

Итак, создаем будущий gem с именем tail

rails plugin new tail --mountable

Ключ --mountable собственно и отделяет создание mountable engine от относительно простого плагина.

В моем случае я писал
rails plugin new tail --mountable --skip-active-record
Поскольку не использую работу с базой.

Очень забавная команда. Если посмотреть на структуру папок, созданную в результате, то это будет смесь обычного Rails приложения и результата команды bundle gem.

Три основных момента:


Первое

папка lib будет основой будущего gem'a и самый главный файл в нем — engine.rb
module Tail
  class Engine < ::Rails::Engine
    isolate_namespace Tail
  end
end

Создается модуль с именем gem'a с изолированным пространством имен, в который будут завернуты все ваши модели, контроллеры, вьюхи и классы. Т.е. вместо класса SomeClass у вас будет Tail::SomeClass, то же самое с routes и путями — вместо, например, messages_path вы будете писать tail.messages_path, если нужно будет попасть в веб-часть gem'a.
Само-собой, это делается для того, чтобы исключить конфликты имен gem'a и приложений, куда он будет монтироваться.

Обратите внимание на структуру папок app/ в приложении: в каждую из них добавлена дополнительная подпапка с названием gem'a, к которую складываются нужные файлы. Влияет на пути к файлам и хелперам.
Скрытый текст


Второе,

файл .gemspec — кроме описания песональной информации в нем содержится список зависимостей от других gem'ов и список файлов, необходимых для сборки.

lib/version.rb — номер версии gem'a. Рекомендуется использовать нотацию, описанную в semver.org. Простая штука, но обращаю на нее внимание, потому что очередная публикация gem'a без изменения версии не допускается.

И третье:

папка test/dummy — здесь содержится фейковое приложение, которое уже смонтировано (/test/dummy/config/routes.rb). Т.е для того, чтобы проверить работы gem'a, достаточно перейти в эту папкe и запустить rails server. По адресу localhost:3000/tail будет мое приложение. Действительно очень удобно.
Скрытый текст


Наполняем gem


rails generate resource log 

Не использую scaffold, поскольку нет необходимости в CRUD и генерации представлений.

Переношу в сгенерированные файлы код из существующих классов. Стоит убедиться, что все работает, запустив фейковое приложение из test/dummy.

Всё, с этого момента gem можно подключать и отлаживать в другом приложении:
gem 'tail', path: '~/projects/tail'

или
gem 'tail' , git: 'git://github.com/k2m30/tail.git' #после коммита и push на github

Главное не забывать обновлять файл с версиями и делать bundle update tail в приложении, использующим gem после каждого изменения.

Публикация


Чтобы опубликовать gem на rubygems, нужно зарегистрироваться там.

После этого в папке gem-проекта
Для сборки gem
rake build

Вспомогательная команда
gem push

в создании gem'a не участвует, просто это легкий способ указать rubygems credetnials для релиза. Нужно ввести только один раз.

Собственно публикация.
rake release

Сразу после этого gem появится в поиске на rubygems и станет доступным через gem install для других разработчиков.

Итого


1. Чтобы создать mountable Rails gem, нужно сделать следующее:
создать скелет
rails plugin new tail --mountable

2. Наполнить его привычным Rails приложением
Есть незначительные особенности, но их мало и они хорошо описаны
Очень просто посмотреть, что же вы сделали, запустив rails server в папке test/dummy/

3. Собрать gem
rake build

4. Зарегистрироваться на rubygems.org и указать свои логин и пароль:
gem push

Сделать релиз
rake release

Вот и всё, вы счастливый обладатель собственного gem'a

К слову сказать, на момент публикации rubygems утверждает, что мой gem скачали уже 151 раз. Не очень верится, но приятно.

Вообще при разработке упор делался на простоту установки и использования — ничего лишнего. Совсем. Хотя есть и другие варианты — например, gem webtail отправляет содержимое лога в сокет и, соответственно, можно смотреть изменения логов в реальном времени. Меня такая реализация не устраивает, хотя бы потому, что нужна дополнительная пляска с фаерволами. Хотя красиво.

Очень надеюсь, что моя статья, и, кто его знает, сам gem окажутся небесполезными и сэкономят кому-нибудь время и нервы.



Ссылки


http://wangjohn.github.io/railties/rails/gsoc/2013/07/10/introduction-to-railties.html
http://blog.thepete.net/2010/11/creating-and-publishing-your-first-ruby.html
Rails по-русски
Сам gem на github.com
Он же на rubygems.org
Tags:
Hubs:
+23
Comments11

Articles

Change theme settings