vx-lib-logger 0.1.1 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1d28721e11382b1f0383f2cf07b19fc9098312b1
4
- data.tar.gz: 2d0c3698efe175863557797b011ba0d39c921ec1
3
+ metadata.gz: 3cd5b9c66ac3665ca6ec8c71115a1a0c92a75a49
4
+ data.tar.gz: 580fad2cf11354a4f0712482cf3a2074b04b2578
5
5
  SHA512:
6
- metadata.gz: ca6ce7efc36837cd479a8e54f724063014bba8eeacf66bb6dc26883e23f9e1af18366873292b9ad1632da06099ed869a0fcf58530b11e657fb9e2da004867409
7
- data.tar.gz: a8ef47f348bffb01310cb89c80d8a45bda8e0d537c3e0bb01779a304ed733b767895f92219ec7d994a8c57e17ba8a08091e45a9c1d4627cd7e7f166d63e8521e
6
+ metadata.gz: bdfabb6e1f5ea36ef83d26b3af50c44004174937bbd23a474c26cfb7b9f9d3c4422fe51d6182f7170e86d4803ae3f03e72719a1e545bd29bd6816e419b8f741a
7
+ data.tar.gz: f9610c3f29159002a469e8ab2edd16ca5fed2326bea24ed7f28cfdd52c579ed088fd5f8731c709b5ea5d46c1eb07266f1c2f1b283ee98b6a93876c66a8169138
data/lib/vx/lib/logger.rb CHANGED
@@ -5,17 +5,20 @@ module Vx ; module Lib
5
5
  module Logger
6
6
 
7
7
  autoload :Instance, File.expand_path("../logger/instance", __FILE__)
8
- autoload :JsonFormatter, File.expand_path("../logger/json_formatter", __FILE__)
9
- autoload :RawFormatter, File.expand_path("../logger/raw_formatter", __FILE__)
8
+ autoload :StdoutFormatter, File.expand_path("../logger/stdout_formatter", __FILE__)
9
+ autoload :JournalFormatter, File.expand_path("../logger/journal_formatter", __FILE__)
10
+ autoload :Logstash, File.expand_path("../logger/logstash", __FILE__)
11
+ autoload :Sanitizer, File.expand_path("../logger/sanitizer", __FILE__)
10
12
 
11
13
  module Rack
12
14
  autoload :HandleExceptions, File.expand_path("../logger/rack/handle_exceptions", __FILE__)
13
15
  end
14
16
 
15
- @@default = Instance.new(STDOUT)
17
+ @@logstash = Logstash.new
18
+ @@default = Instance.new(STDOUT, logstash: @@logstash)
16
19
 
17
20
  def self.get(io = nil, options = {})
18
- Instance.new(io || STDOUT, options)
21
+ Instance.new(io || STDOUT, options.merge(logstash: @@logstash))
19
22
  end
20
23
 
21
24
  def self.default
@@ -1,16 +1,33 @@
1
1
  require 'json'
2
2
  require 'thread'
3
+ require 'socket'
3
4
 
4
5
  module Vx ; module Lib ; module Logger
5
6
 
6
7
  class Instance
7
8
 
8
- attr_reader :params, :logger
9
+ attr_reader :params, :logger, :logstash
9
10
 
10
11
  def initialize(io, params = {})
11
- @params = params
12
+ @logstash = params.delete(:logstash)
13
+ @params = params
14
+
15
+ @progname = @params.delete(:progname)
16
+
17
+ @progname ||= begin
18
+ pname = $PROGRAM_NAME
19
+ pname && File.basename(pname)
20
+ end
21
+
22
+ @formatter = ->(_,_,_,m) { m }
23
+
12
24
  @logger = ::Logger.new(io, 7, 50_000_000)
13
- @logger.formatter = RawFormatter.new
25
+ @logger.formatter = @formatter
26
+ @logger.progname = @progname
27
+
28
+ @logstash_logger = ::Logger.new(logstash)
29
+ @logstash_logger.formatter = @formatter
30
+ @logstash_logger.progname = @progname
14
31
  end
15
32
 
16
33
  [:fatal, :warn, :debug, :error, :info].each do |m|
@@ -32,7 +49,7 @@ module Vx ; module Lib ; module Logger
32
49
  end
33
50
 
34
51
  def progname=(new_val)
35
- params[:progname] = new_val
52
+ @progname = new_val
36
53
  end
37
54
 
38
55
  def handle(message, options = {})
@@ -64,29 +81,44 @@ module Vx ; module Lib ; module Logger
64
81
  def process_message(level, message, options = {})
65
82
 
66
83
  if options[:exception] && options[:exception].is_a?(Exception)
67
- ex = options[:exception]
84
+ ex = options.delete(:exception)
68
85
  options.merge!(
69
- exception: [ex.class.to_s, ex.message],
86
+ exception: [ex.class.to_s, ex.message].join(" - "),
70
87
  backtrace: (ex.backtrace || []).map(&:to_s).join("\n"),
71
88
  )
72
89
  end
73
90
 
74
91
  body = {
75
92
  thread_id: ::Thread.current.object_id,
76
- process_id: ::Process.pid,
77
93
  }
78
94
 
79
95
  if options && options != {}
96
+
97
+ duration = options.delete(:duration)
98
+ if duration && duration.respond_to?(:to_f)
99
+ body.merge!(duration: duration.to_f)
100
+ end
101
+
80
102
  body.merge!(
81
- fields: options
103
+ fields: Sanitizer.hash(options)
82
104
  )
83
105
  end
84
106
 
85
- @logger.public_send level, format_message(message, body)
107
+
108
+ @logger.public_send level, format_stdout(level, message, body)
109
+
110
+ if logstash.enabled?
111
+ @logstash_logger.public_send level, format_journal(level, message, body)
112
+ end
86
113
  end
87
114
 
88
- def format_message(message, payload)
89
- JsonFormatter.call(message, payload)
115
+ def format_stdout(level, message, payload)
116
+ StdoutFormatter.call(level, message, payload)
117
+ end
118
+
119
+ def format_journal(level, message, payload)
120
+ JournalFormatter.call(level, @progname, message, payload)
121
+
90
122
  end
91
123
 
92
124
  end
@@ -0,0 +1,31 @@
1
+ require 'oj'
2
+
3
+ module Vx ; module Lib ; module Logger
4
+
5
+ module JournalFormatter
6
+
7
+ @@exe = $PROGRAM_NAME
8
+ @@gid = ::Process.gid
9
+ @@uid = ::Process.uid
10
+ @@pid = ::Process.pid
11
+ @@host = ::Socket.gethostname
12
+
13
+ def self.call(level, progname, message, payload)
14
+ m = ::Oj.dump(
15
+ payload.merge(
16
+ MESSAGE: message.to_s,
17
+ SYSLOG_IDENTIFIER: progname,
18
+ _EXE: @@exe,
19
+ _GID: @@gid,
20
+ _UID: @@uid,
21
+ _PID: @@pid,
22
+ _HOSTNAME: @@host,
23
+ level: level,
24
+ ),
25
+ mode: :compat
26
+ )
27
+ m.encode("UTF-8", invalid: :replace, replace: "?") << "\n"
28
+ end
29
+ end
30
+
31
+ end ; end ; end
@@ -0,0 +1,87 @@
1
+ require 'socket'
2
+ require 'uri'
3
+ require 'thread'
4
+
5
+ module Vx ; module Lib ; module Logger
6
+
7
+ class Logstash
8
+
9
+ def initialize
10
+ @mutex = Mutex.new
11
+ end
12
+
13
+ def uri
14
+ @uri ||= begin
15
+ h = ENV['LOGSTASH_HOST']
16
+ URI("logstash://#{h}") if h
17
+ end
18
+ end
19
+
20
+ def enabled?
21
+ !!uri
22
+ end
23
+
24
+ def connected?
25
+ !!@io
26
+ end
27
+
28
+ def close
29
+ @io && @io.close
30
+ rescue Exception => e
31
+ warn "#{self.class} - #{e.class} - #{e.message}"
32
+ ensure
33
+ @io = nil
34
+ end
35
+
36
+ def write(message)
37
+ if enabled?
38
+ @mutex.synchronize do
39
+ with_connection do
40
+ @io.write message
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ def flush
47
+ @io && @io.flush
48
+ end
49
+
50
+ private
51
+
52
+ def host
53
+ uri.host
54
+ end
55
+
56
+ def port
57
+ @port ||= uri.port || 514
58
+ end
59
+
60
+ def warn(msg)
61
+ $stderr.puts msg
62
+ end
63
+
64
+ def with_connection(&block)
65
+ connect unless connected?
66
+ yield
67
+ rescue Exception => e
68
+ warn "#{self.class} - #{e.class} - #{e.message}"
69
+ close
70
+ @io = nil
71
+ end
72
+
73
+ def reconnect
74
+ @io = nil
75
+ connect
76
+ end
77
+
78
+ def connect
79
+ @io = TCPSocket.new(host, port).tap do |socket|
80
+ warn "#{self.class} - connect to #{uri.to_s}"
81
+ socket.sync = true
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ end ; end ; end
@@ -0,0 +1,20 @@
1
+ module Vx ; module Lib ; module Logger
2
+
3
+ module Sanitizer
4
+
5
+ def self.hash(payload)
6
+ payload = {} unless payload.is_a?(Hash)
7
+
8
+ payload.keys.each do |key_name|
9
+ value = payload[key_name]
10
+ unless value.is_a?(String)
11
+ payload[key_name] = value.to_s
12
+ end
13
+ end
14
+
15
+ payload
16
+ end
17
+
18
+ end
19
+
20
+ end ; end ; end
@@ -2,11 +2,11 @@ require 'oj'
2
2
 
3
3
  module Vx ; module Lib ; module Logger
4
4
 
5
- module JsonFormatter
5
+ module StdoutFormatter
6
6
 
7
- def self.call(message, payload)
7
+ def self.call(level, message, payload)
8
8
  payload = ::Oj.dump(payload, mode: :compat)
9
- "#{message} :--: #{payload}"
9
+ "[#{level}] #{message} :--: #{payload}\n"
10
10
  end
11
11
 
12
12
  end
@@ -1,7 +1,7 @@
1
1
  module Vx
2
2
  module Lib
3
3
  module Logger
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
6
6
  end
7
7
  end
@@ -4,77 +4,91 @@ require 'stringio'
4
4
 
5
5
  describe Vx::Lib::Logger::Instance do
6
6
 
7
- before do
8
- @out = StringIO.new
9
- @log = Vx::Lib::Logger.get(@out)
10
- assert @log
11
- end
12
-
13
- [:fatal, :warn, :debug, :error, :info].each do |m|
14
- it "should write #{m} message" do
15
- @log.public_send(m, "send #{m}")
16
- text = "[#{m.upcase}] send #{m} :--: {\"thread_id\":#{tid},\"process_id\":#{pid}}\n"
17
- assert_equal get_out, text
18
- end
19
- end
20
-
21
- it "should write message with params" do
22
- @log.info "text message", param: :value
23
- text = "[INFO] text message :--: {\"thread_id\":#{tid},\"process_id\":#{pid},\"fields\":{\"param\":\"value\"}}\n"
24
- assert_equal get_out, text
25
- end
26
-
27
- it "should write message with object in params" do
28
- @log.info "text message", param: self
29
- assert get_out
30
- end
31
-
32
- it "should write message with exception in params" do
33
- @log.info "text message", exception: Exception.new("got!")
34
- text = "[INFO] text message :--: {\"thread_id\":#{tid},\"process_id\":#{pid},\"fields\":{\"exception\":[\"Exception\",\"got!\"],\"backtrace\":\"\"}}\n"
35
- assert_equal get_out, text
36
- end
37
-
38
- it "should handle block" do
39
- @log.handle "text message" do
40
- sleep 0.1
41
- end
42
- assert_match(/duration/, get_out)
43
-
44
- begin
45
- @log.handle "text message", key: :value do
46
- raise 'got!'
47
- end
48
- rescue Exception
49
- end
50
-
51
- body = get_out
52
- assert_match(/duration/, body)
53
- assert_match(/key/, body)
54
- assert_match(/value/, body)
55
- assert_match(/got\!/, body)
56
- assert_match(/backtrace/, body)
57
- end
58
-
59
- it "should dump invalid unicode key" do
60
- @log.info "Le Caf\xc3\xa9 \xa9", key: "Le Caf\xc3\xa9 \xa9"
61
- text = "[INFO] Le Café \xA9 :--: {\"thread_id\":#{tid},\"process_id\":#{pid},\"fields\":{\"key\":\"Le Café \xA9\"}}\n"
62
- assert_equal get_out, text
63
- end
64
-
65
- def get_out
66
- @out.rewind
67
- body = @out.read
68
- @out.rewind
69
- body
70
- end
71
-
72
- def tid
73
- Thread.current.object_id
74
- end
75
-
76
- def pid
77
- Process.pid
78
- end
7
+ before do
8
+ @out = StringIO.new
9
+ @log = Vx::Lib::Logger.get(@out)
10
+ assert @log
11
+ end
12
+
13
+ [:fatal, :warn, :debug, :error, :info].each do |m|
14
+ it "should write #{m} message" do
15
+ @log.public_send(m, "send #{m}")
16
+ text = "[#{m}] send #{m} :--: {\"thread_id\":#{tid}}\n"
17
+ assert_equal text, get_out
18
+ end
19
+ end
20
+
21
+ it "should write message with params" do
22
+ @log.info "text message", param: :value
23
+ text = "[info] text message :--: {\"thread_id\":#{tid},\"fields\":{\"param\":\"value\"}}\n"
24
+ assert_equal text, get_out
25
+ end
26
+
27
+ it "should write message with object in params" do
28
+ @log.info "object message", param: Exception.new("foo")
29
+ assert get_out
30
+ end
31
+
32
+ it "should write message with exception in params" do
33
+ @log.info "text message", exception: Exception.new("got!")
34
+ text = "[info] text message :--: {\"thread_id\":#{tid},\"fields\":{\"exception\":\"Exception - got!\",\"backtrace\":\"\"}}\n"
35
+ assert_equal text, get_out
36
+ end
37
+
38
+ it "should handle block" do
39
+ @log.handle "text message" do
40
+ sleep 0.1
41
+ end
42
+ assert_match(/duration/, get_out)
43
+
44
+ begin
45
+ @log.handle "text message", key: :value do
46
+ raise 'got!'
47
+ end
48
+ rescue Exception
49
+ end
50
+
51
+ body = get_out
52
+ assert_match(/duration/, body)
53
+ assert_match(/key/, body)
54
+ assert_match(/value/, body)
55
+ assert_match(/got\!/, body)
56
+ assert_match(/backtrace/, body)
57
+ end
58
+
59
+ it "should dump invalid unicode key" do
60
+ @log.info "Le Caf\xc3\xa9 \xa9", key: "Le Caf\xc3\xa9 \xa9"
61
+ text = "[info] Le Café \xA9 :--: {\"thread_id\":#{tid},\"fields\":{\"key\":\"Le Café \xA9\"}}\n"
62
+ assert_equal text, get_out
63
+ end
64
+
65
+ it "should write message with logstash" do
66
+ begin
67
+ ENV['LOGSTASH_HOST'] = 'localhost:9999'
68
+ log = Vx::Lib::Logger.get(@out)
69
+ re = with_socket do
70
+ log.info "Hello"
71
+ end
72
+ assert_match(/Hello/, re)
73
+ assert_match(/Hello/, get_out)
74
+ ensure
75
+ ENV['LOGSTASH_HOST'] = nil
76
+ end
77
+ end
78
+
79
+ def get_out
80
+ @out.rewind
81
+ body = @out.read
82
+ @out.rewind
83
+ body
84
+ end
85
+
86
+ def tid
87
+ Thread.current.object_id
88
+ end
89
+
90
+ def pid
91
+ Process.pid
92
+ end
79
93
 
80
94
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vx::Lib::Logger::Logstash do
4
+
5
+ before do
6
+ ENV['LOGSTASH_HOST'] = 'localhost:9999'
7
+ end
8
+
9
+ after do
10
+ ENV['LOGSTASH_HOST'] = nil
11
+ end
12
+
13
+ it "should successfuly connect" do
14
+ re = with_socket do
15
+ log = Vx::Lib::Logger::Logstash.new
16
+ log.write("Hello\n")
17
+ log.close
18
+ end
19
+ assert_equal re, "Hello\n"
20
+ end
21
+
22
+ it "should successfuly lost connection" do
23
+ log = Vx::Lib::Logger::Logstash.new
24
+
25
+ re = with_socket do
26
+ log.write("Hello\n")
27
+ log.close
28
+ end
29
+
30
+ log.write("Lost\n")
31
+
32
+ re << with_socket do
33
+ log.write("World\n")
34
+ end
35
+
36
+ log.close
37
+
38
+ assert_equal re, "Hello\nWorld\n"
39
+ end
40
+
41
+
42
+ end
data/spec/spec_helper.rb CHANGED
@@ -2,3 +2,25 @@ require File.expand_path("../../lib/vx/lib/logger", __FILE__)
2
2
 
3
3
  require 'minitest/spec'
4
4
  require 'minitest/autorun'
5
+
6
+ def with_socket
7
+ out = ""
8
+ server = TCPServer.new 9999
9
+
10
+ th = Thread.new do
11
+ loop do
12
+ client = server.accept
13
+ out << client.gets
14
+ client.close
15
+ end
16
+ end
17
+
18
+ begin
19
+ yield
20
+ sleep 0.2
21
+ out
22
+ ensure
23
+ th.kill
24
+ server.close
25
+ end
26
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vx-lib-logger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Galinsky
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-02 00:00:00.000000000 Z
11
+ date: 2015-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oj
@@ -66,12 +66,15 @@ files:
66
66
  - Rakefile
67
67
  - lib/vx/lib/logger.rb
68
68
  - lib/vx/lib/logger/instance.rb
69
- - lib/vx/lib/logger/json_formatter.rb
69
+ - lib/vx/lib/logger/journal_formatter.rb
70
+ - lib/vx/lib/logger/logstash.rb
70
71
  - lib/vx/lib/logger/rack/handle_exceptions.rb
71
- - lib/vx/lib/logger/raw_formatter.rb
72
+ - lib/vx/lib/logger/sanitizer.rb
73
+ - lib/vx/lib/logger/stdout_formatter.rb
72
74
  - lib/vx/lib/logger/version.rb
73
75
  - spec/lib/instance_spec.rb
74
76
  - spec/lib/logger_spec.rb
77
+ - spec/lib/logstash_spec.rb
75
78
  - spec/lib/rack_handle_exceptions_spec.rb
76
79
  - spec/spec_helper.rb
77
80
  - vexor.yml
@@ -103,6 +106,6 @@ summary: summary
103
106
  test_files:
104
107
  - spec/lib/instance_spec.rb
105
108
  - spec/lib/logger_spec.rb
109
+ - spec/lib/logstash_spec.rb
106
110
  - spec/lib/rack_handle_exceptions_spec.rb
107
111
  - spec/spec_helper.rb
108
- has_rdoc:
@@ -1,10 +0,0 @@
1
- module Vx ; module Lib ; module Logger
2
-
3
- RawFormatter = Struct.new(:parent) do
4
-
5
- def call(severity, datetime, progname, msg)
6
- "[#{severity}] #{msg.to_s}\n"
7
- end
8
- end
9
-
10
- end ; end ; end