u-log 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b29ab5eaabe74647d865740ccd810a406a31e103
4
+ data.tar.gz: 2ebf738981d7231f0035b34fc0354694eb04069e
5
+ SHA512:
6
+ metadata.gz: fdf58a1d3f8161dcbd6354d39eeff60ea1e59c2cc2f47b3861e2b84e1084fc8b486cf5049ebc1632fee8f343babe14edf5f47031483b6c57d31b4bf20a694227
7
+ data.tar.gz: f940536cc3aa21e48940ee588827189e1741df56ab4479479f990a88ea357fb8e93a79a8d9837694f2defb49c12c4290fec2ae27cd1346600392dd83213989c2
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "https://rubygems.org"
2
+ gemspec
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ u-log (0.0.1)
5
+ lines
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ benchmark-ips (1.2.0)
11
+ lines (0.2.0)
12
+ minitest (5.3.3)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ benchmark-ips
19
+ minitest
20
+ u-log!
@@ -0,0 +1,100 @@
1
+ U::Log - a different take on logging
2
+ ====================================
3
+
4
+ [![Build
5
+ Status](https://travis-ci.org/zimbatm/u-log.svg?branch=master)](https://travis-ci.org/zimbatm/u-log)
6
+
7
+ An oppinionated logging library.
8
+
9
+ * Log everything in development AND production.
10
+ * Logs should be easy to read, grep and parse.
11
+ * Logging something should never fail.
12
+ * Let the system handle the storage. Write to syslog or STDERR.
13
+ * No log levels necessary. Just log whatever you want.
14
+
15
+ STATUS: ALPHA
16
+ =============
17
+
18
+ Doc is still scarce so it's quite hard to get started. I think reading the
19
+ lib/u-log.rb should give a good idea of the capabilities.
20
+
21
+ It would be nice to expose a method that resolves a context into a hash. It's
22
+ useful to share the context with other tools like an error reporter. Btw,
23
+ Sentry/Raven is great.
24
+
25
+ Quick intro
26
+ -----------
27
+
28
+ ```ruby
29
+ require 'u-log'
30
+
31
+ # Setups the outputs. IO and Syslog are supported.
32
+ l = U::Log.new($stderr, Lines,
33
+ at: ->{ Time.now.utc },
34
+ pid: ->{ Process.pid },
35
+ )
36
+
37
+ # First example
38
+ l.log(foo: 'bar') # logs: at=2013-07-14T14:19:28Z foo=bar
39
+
40
+ # If not a hash, the argument is transformed. A second argument is accepted as
41
+ # a hash
42
+ l.log("Hey", count: 3) # logs: at=2013-07-14T14:19:28Z msg=Hey count=3
43
+
44
+ # You can also keep a context
45
+ class MyClass < ActiveRecord::Base
46
+ attr_reader :lines
47
+ def initialize(logger)
48
+ @logger = logger.context(my_class_id: self.id)
49
+ end
50
+
51
+ def do_something
52
+ logger.log("Something happened")
53
+ # logs: at=2013-07-14T14:19:28Z msg='Something happeend' my_class_id: 2324
54
+ end
55
+ end
56
+ ```
57
+
58
+ Features
59
+ --------
60
+
61
+ * Simple to use
62
+ * Thread safe (if the IO#write is)
63
+ * Designed to not raise exceptions (unless it's an IO issue)
64
+ * A backward-compatible Logger is provided in case you want to retrofit
65
+ * require "u-log/active_record" for sane ActiveRecord logs
66
+ * "u-log/rack_logger" is a logging middleware for Rack
67
+
68
+ Known issues
69
+ ------------
70
+
71
+ Syslog seems to truncate lines longer than 2056 chars and Lines makes if very
72
+ easy to put too much data.
73
+
74
+ Lines logging speed is reasonable but it could be faster. It writes at around
75
+ 5000 lines per second to Syslog on my machine.
76
+
77
+ Inspired by
78
+ -----------
79
+
80
+ * Scrolls : https://github.com/asenchi/scrolls
81
+ * Lograge : https://github.com/roidrage/lograge
82
+
83
+
84
+
85
+
86
+
87
+ * Very simple (aka. fast)
88
+ * No log level
89
+
90
+
91
+
92
+ TODO
93
+ ----
94
+
95
+ Error reporting
96
+
97
+ Create message context
98
+
99
+
100
+ Because the log context is hierarchical,
@@ -0,0 +1,26 @@
1
+ require 'benchmark/ips'
2
+ require 'stringio'
3
+
4
+ data = 100.times.map{ 'sadfljkasfjlk' }
5
+
6
+ Benchmark.ips do |x|
7
+ x.report "join" do |n|
8
+ n.times do
9
+ data.join
10
+ end
11
+ end
12
+ x.report "StringIO" do |n|
13
+ n.times do
14
+ s = StringIO.new('wb')
15
+ data.each do |y|
16
+ s.write y
17
+ end
18
+ s.string
19
+ end
20
+ end
21
+ x.report "inject+" do |n|
22
+ n.times do
23
+ data.inject(&:+)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,113 @@
1
+ # U::Log is an opinionated logging library.
2
+ #
3
+ # The main take is that logs are structured.
4
+ # Too much time is spend formatting logs, they should look nice by default.
5
+ # That's why U::Log uses the lines format by default.
6
+ #
7
+ # Log everything in development AND production.
8
+ # Logs should be easy to read, grep and parse.
9
+ # Logging something should never fail.
10
+ # Let the system handle the storage. Write to syslog or STDERR.
11
+ # No log levels necessary. Just log whatever you want.
12
+ #
13
+ # Example:
14
+ #
15
+ # U.log("Oops !", foo: {}, g: [])
16
+ # #outputs:
17
+ # # at=2013-03-07T09:21:39Z pid=3242 app=some-process msg="Oops !" foo={} g=[]
18
+ #
19
+ # Usage:
20
+ #
21
+ #
22
+ # U.log(foo: 3, msg: "This")
23
+ #
24
+ # ctx = U.log_context(encoding_id: Log.id)
25
+ # ctx.log({})
26
+ #
27
+ module U; module Log
28
+ # A very simple logger and log context
29
+ #
30
+ class Logger
31
+ NL = "\n".freeze
32
+
33
+ # +out+ is the log destination. Has a #<< method that takes a string.
34
+ # +format+ is what transforms the data into a string using the #dump method
35
+ # +data+ is context data for the logger. Responds to #to_h.
36
+ def initialize(out, format, data)
37
+ @out = out
38
+ @format = format
39
+ @data = data.to_h
40
+ end
41
+
42
+ # Outputs the given arguments merged with the context.
43
+ def log(*args)
44
+ @out << with_data(args_to_hash args).to_s + NL
45
+ end
46
+
47
+ # Creates a derivative context so that `context(a: 1).context(b: 2)`
48
+ # is equivalent to `contect(a: 1, b: 2)`
49
+ def context(data = {})
50
+ return self unless data.to_h.any?
51
+ with_data @data.merge(data.to_h)
52
+ end
53
+
54
+ alias merge context
55
+
56
+ def to_s
57
+ @format.dump(evaluate_procs @data)
58
+ end
59
+
60
+ def to_h; @data; end
61
+
62
+ # Returns a ::Logger-compatible object.
63
+ #
64
+ # Make sure to require 'u-log/compat' before invoking this method.
65
+ #
66
+ def compat; Compat.new(self) end
67
+
68
+ protected
69
+
70
+ def with_data(data)
71
+ self.class.new @out, @format, data
72
+ end
73
+
74
+ def evaluate_procs(obj)
75
+ obj.each_with_object({}) do |(k,v), merged|
76
+ merged[k] = v.respond_to?(:call) ? (v.call rescue $!) : v
77
+ end
78
+ end
79
+
80
+ def args_to_hash(args)
81
+ return {} if args.empty?
82
+ data = @data.dup
83
+ # Allow the first argument to be a message
84
+ if !args.first.respond_to? :to_h
85
+ data.merge!(msg: args.shift)
86
+ end
87
+ args.inject(data) do |h, obj|
88
+ h.merge! obj.to_h
89
+ end
90
+ data
91
+ end
92
+ end
93
+ end end
94
+
95
+ require 'lines'
96
+
97
+ module U
98
+ class << self
99
+ # Default logger that outputs to stderr with the Logfmt format
100
+ attr_accessor :logger
101
+
102
+ # shortcut for U.logger.log
103
+ def log(*args); logger.log(*args); end
104
+ # shotcut for U.logger.context
105
+ def log_context(data={}); logger.context(data); end
106
+ end
107
+
108
+ # Default global
109
+ self.logger = Log::Logger.new($stderr, Lines,
110
+ at: ->{ Time.now.utc },
111
+ pid: ->{ Process.pid },
112
+ )
113
+ end
@@ -0,0 +1,68 @@
1
+ require 'active_record'
2
+ require 'active_record/log_subscriber'
3
+ require 'u-log'
4
+ require 'u-log/compat'
5
+
6
+ module U; module Log
7
+ class ActiveRecordSubscriber < ActiveSupport::LogSubscriber
8
+ def render_bind(column, value)
9
+ if column
10
+ if column.binary?
11
+ value = "<#{value.bytesize} bytes of binary data>"
12
+ end
13
+
14
+ [column.name, value]
15
+ else
16
+ [nil, value]
17
+ end
18
+ end
19
+
20
+ def sql(event)
21
+ payload = event.payload
22
+
23
+ return if payload[:name] == 'SCHEMA' || payload[:name] == 'EXPLAIN'
24
+
25
+ args = {}
26
+
27
+ args[:name] = payload[:name] if payload[:name]
28
+ args[:sql] = payload[:sql].squeeze(' ')
29
+
30
+ if payload[:binds] && payload[:binds].any?
31
+ args[:binds] = payload[:binds].inject({}) do |hash,(col, v)|
32
+ k, v = render_bind(col, v)
33
+ hash[k] = v
34
+ hash
35
+ end
36
+ end
37
+
38
+ args[:elapsed] = [event.duration.round(1), 'ms']
39
+
40
+ logger.ulogger.log(args)
41
+ end
42
+
43
+ def identity(event)
44
+ logger.ulogger.log(name: event.payload[:name], line: event.payload[:line])
45
+ end
46
+ end
47
+ end end
48
+
49
+ # Replace the base logger with our own compatible logger
50
+ ActiveRecord::Base.logger = U.logger.compat
51
+
52
+ # Subscribe u-log to the AR events
53
+ U::Log::ActiveRecordSubscriber.attach_to :active_record
54
+
55
+ # Remove the default ActiveRecord::LogSubscriber to avoid double outputs
56
+ ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber|
57
+ if subscriber.is_a?(ActiveRecord::LogSubscriber)
58
+ component = :active_record
59
+ events = subscriber.public_methods(false).reject{ |method| method.to_s == 'call' }
60
+ events.each do |event|
61
+ ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener|
62
+ if listener.instance_variable_get('@delegate') == subscriber
63
+ ActiveSupport::Notifications.unsubscribe listener
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,61 @@
1
+ module U; module Log
2
+ # Backward-compatible with the stdlib Logger
3
+ # http://ruby-doc.org/stdlib-2.0/libdoc/logger/rdoc/Logger.html
4
+ class Compat
5
+ LEVELS = {
6
+ 0 => :debug,
7
+ 1 => :info,
8
+ 2 => :warn,
9
+ 3 => :error,
10
+ 4 => :fatal,
11
+ 5 => :unknown,
12
+ }
13
+
14
+ attr_reader :ulogger
15
+
16
+ def initialize(ulogger)
17
+ @ulogger = ulogger
18
+ end
19
+
20
+ def log(severity, message = nil, progname = nil, &block)
21
+ pri = LEVELS[severity] || severity
22
+ if block_given?
23
+ progname = message
24
+ message = yield rescue $!
25
+ end
26
+
27
+ data = { pri: pri }
28
+ data[:app] = progname if progname
29
+ data[:msg] = message if message
30
+
31
+ @logger.log(data)
32
+ end
33
+
34
+ LEVELS.values.each do |level|
35
+ eval "def #{level}(msg=nil, &block); log(:#{level}, msg, &block); end"
36
+ end
37
+
38
+ alias << info
39
+ alias unknown info
40
+
41
+ def noop(*); true end
42
+ %w[add
43
+ clone
44
+ datetime_format
45
+ datetime_format=
46
+ debug?
47
+ info?
48
+ error?
49
+ fatal?
50
+ warn?
51
+ level
52
+ level=
53
+ progname
54
+ progname=
55
+ sev_threshold
56
+ sev_threshold=
57
+ ].each do |op|
58
+ alias_method(op, :noop)
59
+ end
60
+ end
61
+ end end
@@ -0,0 +1,39 @@
1
+ require 'rack/commonlogger'
2
+
3
+ module U; module Log
4
+ class RackLogger < Rack::CommonLogger
5
+ # In development mode the common logger is always inserted
6
+ def self.silence_common_logger!
7
+ Rack::CommonLogger.module_eval('def call(env); @app.call(env); end')
8
+ self
9
+ end
10
+
11
+ def initialize(app, logger)
12
+ @app = app
13
+ @logger = logger
14
+ end
15
+
16
+ def call(env)
17
+ began_at = Time.now
18
+ status, header, body = @app.call(env)
19
+ header = Rack::Utils::HeaderHash.new(header)
20
+ body = Rack::BodyProxy.new(body) { log(env, status, header, began_at) }
21
+ [status, header, body]
22
+ end
23
+
24
+ protected
25
+
26
+ def log(env, status, header, began_at)
27
+ @logger.log(
28
+ remote_addr: env['HTTP_X_FORWARDED_FOR'] || env['REMOTE_ADDR'],
29
+ remote_user: env['REMOTE_USER'] || '',
30
+ method: env['REQUEST_METHOD'],
31
+ path: env['PATH_INFO'],
32
+ query: env['QUERY_STRING'],
33
+ status: status.to_s[0..3],
34
+ length: extract_content_length(header),
35
+ elapsed: [Time.now - began_at, 's'],
36
+ )
37
+ end
38
+ end
39
+ end end
@@ -0,0 +1,3 @@
1
+ module U; module Log
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,23 @@
1
+ $:.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'minitest/autorun'
4
+ require 'stringio'
5
+
6
+ require 'u-log'
7
+
8
+ class U_LoggerTest < MiniTest::Spec
9
+ include U::Log
10
+ NL = "\n"
11
+
12
+ def setup
13
+ @out = StringIO.new
14
+ @ctx = Logger.new(@out, Lines, {})
15
+ end
16
+
17
+ describe 'context' do
18
+ it 'does stuff' do
19
+ @ctx.log('hello')
20
+ assert_equal 'msg=hello' + NL, @out.string
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ # coding: utf-8
2
+ Gem::Specification.new do |spec|
3
+ spec.name = "u-log"
4
+ spec.version = "0.0.1"
5
+ spec.authors = ["zimbatm"]
6
+ spec.email = ["zimbatm@zimbatm.com"]
7
+ spec.summary = %q{a different take on logging}
8
+ spec.description = %q{U::Logger is a very simple logging library made for humans}
9
+ spec.homepage = 'https://github.com/zimbatm/u-logger'
10
+ spec.license = "MIT"
11
+
12
+ spec.files = `git ls-files`.split($/)
13
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
14
+
15
+ spec.add_runtime_dependency "lines"
16
+
17
+ spec.add_development_dependency "benchmark-ips"
18
+ spec.add_development_dependency "minitest"
19
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: u-log
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - zimbatm
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: lines
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: benchmark-ips
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
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: U::Logger is a very simple logging library made for humans
56
+ email:
57
+ - zimbatm@zimbatm.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - Gemfile
63
+ - Gemfile.lock
64
+ - README.md
65
+ - bench_concat.rb
66
+ - lib/u-log.rb
67
+ - lib/u-log/active_record.rb
68
+ - lib/u-log/compat.rb
69
+ - lib/u-log/rack_logger.rb
70
+ - lib/u-log/version.rb
71
+ - test/u-log_test.rb
72
+ - u-log.gemspec
73
+ homepage: https://github.com/zimbatm/u-logger
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.2.2
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: a different take on logging
97
+ test_files:
98
+ - test/u-log_test.rb