Hiển thị Rails log thông qua Live Stream lên trình duyệt

Tản mạn

    MÌnh khá là bị thu hút bời các file Log =))

    Logger là rất quan trọng trong quá trình development và operation một ứng dụng.

    Như tiêu đề bài viết, hôm nay mình sẽ thử stream log tới browser.

    Để dễ tưởng tượng những gì chúng ta sắp làm. Bạn có thể mở file log develoment rails bằng lệnh

tail -f log/development.log

    Những thay đổi trong file log sẽ được hiển thị realtime ở đây.

    Kết quả mong muốn cuối cùng của mình sẽ là tương tự chức năng stream log của datadog. Nhưng phần tách các loại log khó quá nên để sau vầy :3

    tail

Thực hiện

Khởi tạo rails app

    rails new file-streaming-app

Tạo LiveFileStreamsController

touch live_file_streams_controller.rb
class LiveFileStreamsController < ApplicationController
end

Thêm router

resources :live_streams, only: [] do
  collection do
   get :log_file
  end
end

Phát trực tiếp với “response.stream”

    Chúng ta sẽ sử dụng response.stream của ActionController::Streaming

class LiveStreamsController < ApplicationController
  def log_file
    5.times {
      response.stream.write "hello world\n"

      sleep 0.2
    }

    response.stream.close
  end
end

    Bạn có thể xem thêm về ActionController::Streaming tại dây

Xem response ở trình duyệt

rails s

    Vào: localhost:3000/live_streams/log_file

    Chúng ta sẽ thấy “hello world” được in 5 lần trong trình duyệt Response được in ra màn hình cùng một lúc, ngay cả khi đã đặt sleep ở giữa các lần stream.write

    

    Mình sẽ cần xử lí để các nội dung này in ra 1 cách lần lượt

Include ActionController::Live

class LiveStreamsController < ApplicationController
  include ActionController::Live

  def log_file
    response.headers['Content-Type'] = 'text/event-stream'
    response.headers['Last-Modified'] = Time.now.httpdate
  
    5.times {
      response.stream.write "hello world\n"

      sleep 0.2
    }

    response.stream.close
  end
end

    Bây h chúng ta sẽ thấy nội dung được in lần lượt ra màn hình

    Như vậy ta stream được các nội dung thực hiện trong logic. Vậy bây giờ sẽ là tìm cách để stream các thay đổi trong file log

Server-side events

    Nếu chưa bao giờ nghe nói về Server-side events (SSE), có thể đọc thêm về nó ở đây https://www.html5rocks.com/en/tutorials/eventsource/basics/

    Về cơ bản, trình duyệt giữ kết nối mở cho máy chủ và kích hoạt một sự kiện trong JavaScript mỗi khi máy chủ gửi dữ liệu.

Tạo “file_streaming_app / sse.rb”

touch lib/file_streaming_app/sse.rb
require 'json'

module FileStreamingApp
  class SSE
    def initialize(io)
      @io = io
    end

    def write(object)
      @io.write "#{JSON.dump(object)}"
    end

    def close
      @io.close
    end
  end
end

Sử dụng “SSE” bên trong controller

require 'file_streaming_app/sse'

class LiveStreamsController < ApplicationController
  def log_file
    response.headers['Content-Type'] = 'text/event-stream'
    response.headers['Last-Modified'] = Time.now.httpdate

    sse = FileStreamingApp::SSE.new(response.stream)

    5.times {
      sse.write('hello world')

      sleep 0.5
    }
  ensure
    sse.close
  end
end

Sử dụng “filewatcher” để xem các thay đổi trong file

    Để biết khi nào file được thay đổi, có 1 Gem có sẵn khá là tiện dụng đó là filewatcher

gem 'filewatcher', '~> 1.1.1'

    Install gem bằng

bundle install

Đọc file log

touch lib/file_streaming_app/log_file.rb
module FileStreamingApp
  class LogFile
    def added_lines(file_path)
      file_content = File.open(file_path).readlines

      file_content.last(20)
    end
  end
end

Stream file log khi nó được sửa đổi

    Update lại code Controller

def log_file
    response.headers['Content-Type'] = 'text/event-stream'
    response.headers['Last-Modified'] = Time.now.httpdate

    sse = FileStreamingApp::SSE.new(response.stream)

    log_file_path = Rails.root.join('log/development.log').to_s

    file = FileStreamingApp::LogFile.new

    # watch development.log file for changes
    Filewatcher.new([log_file_path]).watch do |_file_path, event_type|
      next unless event_type.to_s.eql?('updated')

      file_lines = file.added_lines(log_file_path)

      sse.write(file_lines)
    end
  ensure
    sse.close
  end

Sửa lại "SSE"

def write(file_lines)
  file_lines.each do |line|
    @io.write line
  end
end

Check thành quả thôi

    

Xử lí đa luồng

    Mặc định, ở môi trường develop rails, các request được xử lí trong 1 luồng. Nên các request sẽ phải chờ request trước đó được thực thi xong.

    Để giải quyết vấn đề đó, đi ăn trộm từ Stack Overflow .

vim config/environments/development.rb
Rails.application.configure do
  # other configurations

  config.middleware.delete Rack::Lock
end

Chỉ Stream các dòng đã thay đổi trong file log

    Chúng ta sẽ cần nhớ vị trí của dòng cuối cùng trong file log trước khi thay đổi và chỉ hiển thị các dòng sau vị trí đó.

    Sửa lại code 1 chút

class LogFile
  def added_lines(file_path)
    file_content = File.open(file_path).readlines
    total_lines = file_content.length

    @last_known_line_position ||= initial_line_position(total_lines)

    start_position = @last_known_line_position

    @last_known_line_position = total_lines

    file_content[start_position, total_lines]
  end

  private

  def initial_line_position(total_lines)
    return 0 if total_lines.zero? || total_lines <= 20

    # print last 20 lines from the file if event is emitted for the first time
    total_lines - 20
  end
end

    Everithing is OK :V

Nguồn: Viblo

Bình luận
Vui lòng đăng nhập để bình luận
Một số bài viết liên quan