zlogger 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5f2504eea7c5c26ea43263ea56fb570e091779f1
4
+ data.tar.gz: e75a239379af084d808f81d763ae36a54d91f98b
5
+ SHA512:
6
+ metadata.gz: 2ddb23f2c093c9fd1f41f8451b3af68e3433495ba2fc6ed45da12aa36aa0e45d9513464abca13b6ef9102adcef5a28860c24483f5daf7797babac908278dc673
7
+ data.tar.gz: 1711db539434394f50a9ab89e2ab616f8f58646c3ae3b81f0075dd35c05a7a6946362ad66de0c9635a5c8976ee25d78421e0811f14dab20356961fc7ce06abca
data/.gitignore ADDED
@@ -0,0 +1,25 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+
24
+ .idea
25
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in zlogger.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Matt Connolly
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # Zlogger - A distributed logging daemon.
2
+
3
+ This gem provides a daemon that reads log messages from a ZeroMQ socket. Messages are formatted and output to STDOUT.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'zlogger'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install zlogger
18
+
19
+ ## Usage
20
+
21
+ To run the logging daemon:
22
+
23
+ $ zlogger
24
+
25
+ And you when messages are sent to the logger, they will appear in STDOUT formatted with the senders name prefixed to
26
+ each line.
27
+
28
+ The daemon uses two ZeroMQ sockets:
29
+
30
+ * At the bind_address : port => a PULL socket to receive log messages.
31
+ * At the bind_address : port + 1 => a PUB socket where re-formatted lines of the log are sent to.
32
+
33
+ To use the logging client in a Ruby process:
34
+
35
+ require 'zlogger'
36
+
37
+ logger = Zlogger::Client.new
38
+ logger.debug "log this debug message"
39
+
40
+ The `logger` client object behaves like a standard ruby Logger.
41
+
42
+
43
+ ## Contributing
44
+
45
+ 1. Fork it ( https://github.com/[my-github-username]/zlogger/fork )
46
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
47
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
48
+ 4. Push to the branch (`git push origin my-new-feature`)
49
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/zloggerd ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+ require 'zlogger'
5
+ require 'optparse'
6
+
7
+ # Process options
8
+ options = {}
9
+
10
+ opts = OptionParser.new do |opts|
11
+ opts.banner = "Usage: #{$0} [options]"
12
+
13
+ opts.separator ""
14
+ opts.separator "Specific options:"
15
+
16
+ opts.on("-b", "--bind ADDRESS",
17
+ "TCP address to bind to, defaults to 0.0.0.0") do |ext|
18
+ options[:bind_address] = ext
19
+ end
20
+
21
+ opts.on("-p", "--port PORT", Integer, "Port number to bind to") do |n|
22
+ options[:port] = n
23
+ end
24
+
25
+ opts.on("-f", '--file FILE', String, "File to write the log to. File is stored under /log folder") do |file|
26
+ options[:output] = file
27
+ end
28
+
29
+ opts.on("-r", '--rotate', String, "Rotate log file daily") do |file|
30
+ options[:rotate] = true
31
+ end
32
+
33
+ opts.on("-s", "--stdout", "Echo output to STDOUT (only applicable when -f is used)") do
34
+ options[:stdout] = true
35
+ end
36
+
37
+ opts.on_tail("-h", "--help", "Show this message") do
38
+ puts opts
39
+ exit
40
+ end
41
+
42
+ opts.on_tail("-v", "--version", "Show version") do
43
+ puts "Zlogger version #{Zlogger::VERSION}"
44
+ exit
45
+ end
46
+ end
47
+
48
+ opts.parse!(ARGV)
49
+
50
+ # Create Zlogger
51
+
52
+ logger = Zlogger::Daemon.new(options)
53
+ logger.run
data/bin/zlogtail ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+ require 'zlogger'
5
+ require 'optparse'
6
+
7
+ # Process options
8
+ options = { :address => '127.0.0.1' }
9
+
10
+ opts = OptionParser.new do |opts|
11
+ opts.banner = "Usage: #{$0} [options]\n\n" +
12
+ "Tail a Zlogger daemon by subscribing to its broadcast of formatted messages"
13
+
14
+ opts.separator ""
15
+ opts.separator "Specific options:"
16
+
17
+ opts.on("-c", "--connect ADDRESS",
18
+ "TCP address to connect to, defaults to 127.0.0.1") do |ext|
19
+ options[:address] = ext
20
+ end
21
+
22
+ opts.on("-p", "--port PORT", Integer, "Port number to bind to") do |n|
23
+ options[:port] = n
24
+ end
25
+
26
+ opts.on_tail("-h", "--help", "Show this message") do
27
+ puts opts
28
+ exit
29
+ end
30
+
31
+ opts.on_tail("-v", "--version", "Show version") do
32
+ puts "Zlogger version #{Zlogger::VERSION}"
33
+ exit
34
+ end
35
+ end
36
+
37
+ opts.parse!(ARGV)
38
+
39
+ # Create Zlogger Reader
40
+
41
+ logger = Zlogger::Reader.new(options)
42
+ logger.run
data/lib/zlogger.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "zlogger/version"
2
+
3
+ module Zlogger
4
+ autoload :Client, "zlogger/client"
5
+ autoload :Daemon, "zlogger/daemon"
6
+ autoload :Reader, "zlogger/reader"
7
+
8
+ DEFAULT_PORT = 7000
9
+ end
@@ -0,0 +1,87 @@
1
+ require 'logger'
2
+ require 'rbczmq'
3
+
4
+ module Zlogger
5
+ class Client < ::Logger
6
+ attr :options
7
+
8
+ # Create a new logger object. This is the client logging object that sends log messages to the remote log
9
+ # collection daemon. The options hash accepts the following options:
10
+ #
11
+ # :context => An existing ZMQ::Context object. Defaults to creating a new one.
12
+ # :address => The TCP address to connect to.
13
+ # :port => The TCP port to connect to.
14
+ # :name => The name to use as a prefix for log messages, defaults to the process name and pid, eg "rails:1234"
15
+ def initialize(options={})
16
+ @options = options
17
+ super(nil)
18
+ @logdev = LogDevice.new(self)
19
+ @logdev.run_socket_thread
20
+
21
+ @formatter = proc do |severity, time, progname, msg|
22
+ if msg.is_a?(Exception)
23
+ "#{severity}: #{msg.message} (#{msg.class})\n" + (msg.backtrace || []).join("\n")
24
+ else
25
+ "#{severity}: #{msg}"
26
+ end
27
+ end
28
+ end
29
+
30
+ def context
31
+ @context ||= (ZMQ.context || ZMQ::Context.new)
32
+ end
33
+
34
+ def queue
35
+ @queue ||= Queue.new
36
+ end
37
+
38
+ def connect_address
39
+ options[:address] || "127.0.0.1"
40
+ end
41
+
42
+ def port
43
+ options[:port] || Zlogger::DEFAULT_PORT
44
+ end
45
+
46
+ def name
47
+ options[:name] || "#{File.basename($0)}:#{Process.pid}"
48
+ end
49
+
50
+ class LogDevice
51
+ attr :client
52
+
53
+ def initialize(client)
54
+ @client = client
55
+ end
56
+
57
+ def write(message)
58
+ client.queue << message
59
+ end
60
+
61
+ def close()
62
+ client.queue << self
63
+ end
64
+
65
+ # it is not threadsafe to access ZMQ sockets, so we only write to the logging socket from a single thread.
66
+ def run_socket_thread
67
+ @thread ||= Thread.new do
68
+ begin
69
+ socket = client.context.socket :PUB
70
+ socket.connect("tcp://#{client.connect_address}:#{client.port}")
71
+ loop do
72
+ object = client.queue.pop
73
+ break if object == self
74
+ message = ZMQ::Message.new
75
+ message.addstr(client.name)
76
+ message.addstr(object.to_s)
77
+ socket.send_message(message)
78
+ end
79
+ socket.close
80
+ rescue StandardError => e
81
+ puts "Logging socket thread error: #{e}"
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,123 @@
1
+ require 'rbczmq'
2
+ require 'date'
3
+ require 'pathname'
4
+
5
+ module Zlogger
6
+ class Daemon
7
+
8
+ FLUSH_TIMER = 0.1 # flush the log to disk after this many seconds.
9
+
10
+ attr :options
11
+ attr_accessor :output_file
12
+ attr_accessor :log_date
13
+
14
+ def initialize(options={})
15
+ @options = options
16
+ if options[:output]
17
+ @log_date = Date.today
18
+ @output_file = File.new(output_filepath, 'a+')
19
+ end
20
+ end
21
+
22
+ def run
23
+ socket = context.socket :SUB
24
+ socket.subscribe ""
25
+ socket.bind("tcp://#{bind_address}:#{port}")
26
+
27
+ poll_item = ZMQ::Pollitem(socket, ZMQ::POLLIN)
28
+ poller = ZMQ::Poller.new
29
+ poller.register(poll_item)
30
+
31
+ loop do
32
+ begin
33
+ rotate_file if options[:rotate]
34
+
35
+ poller.poll(FLUSH_TIMER)
36
+ if poller.readables.include?(socket)
37
+ message = socket.recv_message
38
+ prefix = message.pop.to_s
39
+ buffer = message.pop.to_s
40
+ buffer.gsub("\r\n", "\n")
41
+ buffer.split("\n").each do |line|
42
+ log(prefix, line)
43
+ end
44
+ end
45
+
46
+ flush
47
+
48
+ rescue Interrupt
49
+ break
50
+ rescue StandardError => e
51
+ log("ZLOGGER::DAEMON", e.to_s)
52
+ end
53
+ end
54
+ end
55
+
56
+ def context
57
+ @context ||= (ZMQ.context || ZMQ::Context.new)
58
+ end
59
+
60
+ def port
61
+ options[:port] ||= DEFAULT_PORT
62
+ end
63
+
64
+ def bind_address
65
+ options[:bind_address] ||= "0.0.0.0"
66
+ end
67
+
68
+ def output
69
+ output_file || $stdout
70
+ end
71
+
72
+ def output_filepath
73
+ filename = options[:output]
74
+ file_extension = File.extname(filename)
75
+ filename = filename.gsub(file_extension, "") unless file_extension == ""
76
+ if options[:rotate]
77
+ filename = "#{ filename }_#{ log_date.to_s }.log"
78
+ else
79
+ filename = "#{ filename }.log"
80
+ end
81
+ Pathname.new(filename).to_s
82
+ end
83
+
84
+ def rotate_file
85
+ if log_date < Date.today
86
+ log_date = Date.today
87
+
88
+ # closes previous day file
89
+ @output_file.close if @output_file # just a fail safe in case that for some reason the output file is nil
90
+
91
+ # assigns file for the new day to output_file attribute
92
+ @output_file = File.new(output_filepath, 'a+')
93
+ end
94
+ end
95
+
96
+ def pub_socket
97
+ @pub_socket ||= begin
98
+ socket = context.socket :PUB
99
+ socket.bind("tcp://#{bind_address}:#{port.to_i + 1}")
100
+ socket
101
+ end
102
+ end
103
+
104
+ def log(prefix, line)
105
+ formatted = "#{Time.now.strftime("%Y%m%d %I:%M:%S.%L")}\t#{prefix}:\t#{line}"
106
+ output.puts(formatted)
107
+ $stdout.puts(formatted) if options[:stdout] && options[:output]
108
+ pub_socket.send(formatted)
109
+ end
110
+
111
+ # flush the output file only if enough elapsed time has occurred since the last flush. We want the log capture to
112
+ # be responsive, and reduce the amount of time waiting for synchronous disk i/o.
113
+ def flush
114
+ if output
115
+ now = Time.now
116
+ if @last_flush.nil? || (now - @last_flush > FLUSH_TIMER)
117
+ output.flush
118
+ @last_flush = now
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,43 @@
1
+ require 'rbczmq'
2
+
3
+ module Zlogger
4
+ class Reader
5
+ attr :options
6
+
7
+ def initialize(options={})
8
+ @options = options
9
+ end
10
+
11
+ def sub_socket
12
+ @sub_socket ||=
13
+ begin
14
+ socket = context.socket :SUB
15
+ socket.subscribe ""
16
+ socket.connect("tcp://#{address}:#{port}")
17
+ socket
18
+ end
19
+ end
20
+
21
+ def context
22
+ @context ||= (ZMQ.context || ZMQ::Context.new)
23
+ end
24
+
25
+ def port
26
+ options[:port] ||= DEFAULT_PORT + 1
27
+ end
28
+
29
+ def address
30
+ options[:address] ||= "0.0.0.0"
31
+ end
32
+
33
+ def run
34
+ begin
35
+ loop do
36
+ puts sub_socket.recv
37
+ end
38
+ rescue Interrupt
39
+ # exit nicely
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module Zlogger
2
+ VERSION = "0.0.2"
3
+ end
data/zlogger.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'zlogger/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "zlogger"
8
+ spec.version = Zlogger::VERSION
9
+ spec.authors = ["Matt Connolly"]
10
+ spec.email = ["matt.connolly@me.com"]
11
+ spec.summary = %q{A distributed logging daemon.}
12
+ spec.description = %q{This gem provides a daemon that reads log messages from a ZeroMQ socket. Messages are formatted and output to STDOUT. }
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "rbczmq", "~> 1.7"
22
+ spec.add_development_dependency "bundler", "~> 1.6"
23
+ spec.add_development_dependency "rake"
24
+ end
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zlogger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Matt Connolly
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rbczmq
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: 'This gem provides a daemon that reads log messages from a ZeroMQ socket.
56
+ Messages are formatted and output to STDOUT. '
57
+ email:
58
+ - matt.connolly@me.com
59
+ executables:
60
+ - zloggerd
61
+ - zlogtail
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - ".gitignore"
66
+ - Gemfile
67
+ - LICENSE.txt
68
+ - README.md
69
+ - Rakefile
70
+ - bin/zloggerd
71
+ - bin/zlogtail
72
+ - lib/zlogger.rb
73
+ - lib/zlogger/client.rb
74
+ - lib/zlogger/daemon.rb
75
+ - lib/zlogger/reader.rb
76
+ - lib/zlogger/version.rb
77
+ - zlogger.gemspec
78
+ homepage: ''
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubyforge_project:
98
+ rubygems_version: 2.2.2
99
+ signing_key:
100
+ specification_version: 4
101
+ summary: A distributed logging daemon.
102
+ test_files: []