zlogger 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []