betterlog 0.1.0 → 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 +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +5 -0
- data/Dockerfile +46 -0
- data/Gemfile +5 -1
- data/LICENSE +10 -199
- data/Makefile +78 -0
- data/Rakefile +7 -3
- data/TODO.md +1 -0
- data/VERSION +1 -1
- data/betterlog/healthz.go +71 -0
- data/betterlog.gemspec +20 -10
- data/bin/betterlog +176 -175
- data/bin/betterlog_pusher +34 -0
- data/cmd/betterlog-server/LICENSE +13 -0
- data/cmd/betterlog-server/main.go +165 -0
- data/{log.yml → config/log.yml} +5 -35
- data/lib/betterlog/global_metadata.rb +9 -14
- data/lib/betterlog/log/event.rb +135 -133
- data/lib/betterlog/log/event_formatter.rb +99 -97
- data/lib/betterlog/log/severity.rb +38 -36
- data/lib/betterlog/log.rb +163 -146
- data/lib/betterlog/log_event_formatter.rb +41 -39
- data/lib/betterlog/logger.rb +88 -0
- data/lib/betterlog/notifiers.rb +28 -0
- data/lib/betterlog/railtie.rb +8 -0
- data/lib/betterlog/version.rb +1 -1
- data/lib/betterlog.rb +13 -7
- data/spec/betterlog/global_metadata_spec.rb +38 -0
- data/spec/betterlog/log_spec.rb +221 -0
- data/spec/betterlog/logger_spec.rb +65 -0
- data/spec/spec_helper.rb +13 -0
- metadata +82 -28
- data/betterdocs.gemspec +0 -53
- data/lib/betterdocs/version.rb +0 -8
- data/lib/betterlog/betterlog_railtie.rb +0 -5
@@ -3,21 +3,16 @@
|
|
3
3
|
# thread-global, this will also attempt to update context of error reporting
|
4
4
|
# tools etc.
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
module Betterlog
|
7
|
+
class GlobalMetadata
|
8
|
+
include Tins::SexySingleton
|
8
9
|
|
9
|
-
|
10
|
-
Thread.current['BP_GLOBAL_METATDATA'] || {}
|
11
|
-
end
|
12
|
-
|
13
|
-
def add(data_hash)
|
14
|
-
data = data_hash | data
|
15
|
-
Honeybadger.context(data_hash)
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
10
|
+
thread_local(:data) { {} }
|
19
11
|
|
20
|
-
|
21
|
-
|
12
|
+
def add(data_hash)
|
13
|
+
data = data_hash | data
|
14
|
+
Notifiers.context(data_hash)
|
15
|
+
self
|
16
|
+
end
|
22
17
|
end
|
23
18
|
end
|
data/lib/betterlog/log/event.rb
CHANGED
@@ -1,163 +1,165 @@
|
|
1
|
-
|
2
|
-
class
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
notify
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
rest: rest,
|
16
|
-
notify: notify,
|
17
|
-
)
|
18
|
-
elsif s = arg.ask_and_send(:to_str)
|
19
|
-
new(
|
20
|
-
({ notify: s } if notify).to_h |
|
21
|
-
{
|
22
|
-
message: s,
|
1
|
+
module Betterlog
|
2
|
+
class Log
|
3
|
+
class Event
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
def self.ify(arg, severity: :debug, notify: nil, rest: {})
|
7
|
+
notify ||= rest.delete(:notify)
|
8
|
+
if e = arg.ask_and_send(:exception)
|
9
|
+
ify(
|
10
|
+
{
|
11
|
+
error_class: e.class.name,
|
12
|
+
message: "#{e.class.name}: #{e.message}",
|
13
|
+
backtrace: e.backtrace,
|
14
|
+
},
|
23
15
|
severity: severity,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
{
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
16
|
+
rest: rest,
|
17
|
+
notify: notify,
|
18
|
+
)
|
19
|
+
elsif s = arg.ask_and_send(:to_str)
|
20
|
+
new(
|
21
|
+
({ notify: s } if notify).to_h |
|
22
|
+
{
|
23
|
+
message: s,
|
24
|
+
severity: severity,
|
25
|
+
} | rest
|
26
|
+
)
|
27
|
+
elsif h = arg.ask_and_send(:to_hash)
|
28
|
+
arg = h | { severity: severity } | rest
|
29
|
+
new(
|
30
|
+
({ notify: h[:message] || arg.to_s } if notify).to_h |
|
31
|
+
arg
|
32
|
+
)
|
33
|
+
else
|
34
|
+
message = "Logging #{arg.inspect}"
|
35
|
+
new(
|
36
|
+
({ notify: message } if notify).to_h |
|
37
|
+
{
|
38
|
+
message: message,
|
39
|
+
severity: severity,
|
40
|
+
} | rest
|
41
|
+
)
|
42
|
+
end
|
41
43
|
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.parse(json)
|
45
|
-
new(JSON.parse(json))
|
46
|
-
rescue JSON::ParserError
|
47
|
-
end
|
48
44
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
data&.key?('emitter')
|
45
|
+
def self.parse(json)
|
46
|
+
new(JSON.parse(json))
|
47
|
+
rescue JSON::ParserError
|
53
48
|
end
|
54
|
-
rescue JSON::ParserError
|
55
|
-
false
|
56
|
-
end
|
57
49
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
end
|
63
|
-
data[:severity] =
|
64
|
-
begin
|
65
|
-
Severity.new((data[:severity] || :debug))
|
66
|
-
rescue
|
67
|
-
Severity.new(:debug)
|
50
|
+
def self.is?(json)
|
51
|
+
if json = json.ask_and_send(:to_str)
|
52
|
+
data = JSON.parse(json).ask_and_send(:to_hash)
|
53
|
+
data&.key?('emitter')
|
68
54
|
end
|
69
|
-
|
70
|
-
|
55
|
+
rescue JSON::ParserError
|
56
|
+
false
|
57
|
+
end
|
71
58
|
|
72
|
-
|
73
|
-
|
74
|
-
|
59
|
+
def initialize(data = {})
|
60
|
+
data = data.symbolize_keys_recursive | meta
|
61
|
+
unless data.key?(:message)
|
62
|
+
data[:message] = "a #{data[:type]} type log message of severity #{data[:severity]}"
|
63
|
+
end
|
64
|
+
data[:severity] =
|
65
|
+
begin
|
66
|
+
Severity.new((data[:severity] || :debug))
|
67
|
+
rescue
|
68
|
+
Severity.new(:debug)
|
69
|
+
end
|
70
|
+
@data = Hash[data.sort_by(&:first)]
|
75
71
|
end
|
76
72
|
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
class BreakCircles
|
74
|
+
def initialize
|
75
|
+
@seen = {}
|
76
|
+
end
|
80
77
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
h.each_with_object({}) { |(k, v), h| h[k.to_s.to_sym] = perform(v) }
|
89
|
-
when a = object.ask_and_send(:to_ary)
|
90
|
-
a.map { |o| perform(o) }
|
91
|
-
when IN_JSON === object
|
92
|
-
object
|
78
|
+
IN_JSON = Tins::ModuleGroup[
|
79
|
+
Float, Integer, NilClass, FalseClass, TrueClass
|
80
|
+
]
|
81
|
+
|
82
|
+
def perform(object)
|
83
|
+
if @seen.key?(object.__id__)
|
84
|
+
:circular
|
93
85
|
else
|
94
|
-
object.
|
86
|
+
@seen[object.__id__] = true
|
87
|
+
case
|
88
|
+
when h = object.ask_and_send(:to_hash)
|
89
|
+
h.each_with_object({}) { |(k, v), h| h[k.to_s.to_sym] = perform(v) }
|
90
|
+
when a = object.ask_and_send(:to_ary)
|
91
|
+
a.map { |o| perform(o) }
|
92
|
+
when IN_JSON === object
|
93
|
+
object
|
94
|
+
else
|
95
|
+
object.to_s
|
96
|
+
end
|
95
97
|
end
|
96
98
|
end
|
97
99
|
end
|
98
|
-
end
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
101
|
+
def as_hash(*a)
|
102
|
+
BreakCircles.new.perform(@data)
|
103
|
+
end
|
103
104
|
|
104
|
-
|
105
|
-
|
106
|
-
|
105
|
+
def to_json(*a)
|
106
|
+
as_hash.to_json(*a)
|
107
|
+
end
|
107
108
|
|
108
|
-
|
109
|
-
|
110
|
-
|
109
|
+
def format(*args)
|
110
|
+
Log::EventFormatter.new(self).format(*args)
|
111
|
+
end
|
111
112
|
|
112
|
-
|
113
|
+
alias to_s format
|
113
114
|
|
114
|
-
|
115
|
-
|
116
|
-
|
115
|
+
def []=(name, value)
|
116
|
+
@data[name.to_sym] = value
|
117
|
+
end
|
117
118
|
|
118
|
-
|
119
|
-
|
120
|
-
|
119
|
+
def [](name)
|
120
|
+
@data[name.to_sym]
|
121
|
+
end
|
121
122
|
|
122
|
-
|
123
|
-
|
124
|
-
|
123
|
+
def severity
|
124
|
+
@data[:severity]
|
125
|
+
end
|
125
126
|
|
126
|
-
|
127
|
-
|
128
|
-
|
127
|
+
def emitter
|
128
|
+
@data[:emitter]
|
129
|
+
end
|
129
130
|
|
130
|
-
|
131
|
-
|
132
|
-
|
131
|
+
def notify?
|
132
|
+
@data[:notify]
|
133
|
+
end
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
135
|
+
def eql?(other)
|
136
|
+
@data.eql? other.instance_variable_get(:@data)
|
137
|
+
end
|
137
138
|
|
138
|
-
|
139
|
+
alias == eql?
|
139
140
|
|
140
|
-
|
141
|
-
|
142
|
-
|
141
|
+
def hash
|
142
|
+
@data.hash
|
143
|
+
end
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
145
|
+
private
|
146
|
+
|
147
|
+
def meta
|
148
|
+
m = {
|
149
|
+
timestamp: Time.now.utc.iso8601(3),
|
150
|
+
pid: $$,
|
151
|
+
program: File.basename($0),
|
152
|
+
severity: :debug,
|
153
|
+
type: 'rails',
|
154
|
+
facility: 'local0',
|
155
|
+
host: (Socket.gethostname rescue nil),
|
156
|
+
thread_id: Thread.current.object_id
|
157
|
+
}
|
158
|
+
if defined? GlobalMetadata
|
159
|
+
m |= GlobalMetadata.data
|
160
|
+
end
|
161
|
+
m
|
162
|
+
end
|
161
163
|
end
|
162
164
|
end
|
163
165
|
end
|
@@ -1,116 +1,118 @@
|
|
1
|
-
|
2
|
-
class
|
3
|
-
|
1
|
+
module Betterlog
|
2
|
+
class Log
|
3
|
+
class EventFormatter
|
4
|
+
include Term::ANSIColor
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
def initialize(event)
|
7
|
+
@event = event
|
8
|
+
end
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
10
|
+
def format(pretty: false, color: false, format: :default)
|
11
|
+
old_coloring, Term::ANSIColor.coloring = Term::ANSIColor.coloring?, color
|
12
|
+
f = cc.log.formats[format] and format = f
|
13
|
+
case pretty
|
14
|
+
when :format
|
15
|
+
format_pattern(format: format)
|
16
|
+
else
|
17
|
+
@event.to_json
|
18
|
+
end
|
19
|
+
ensure
|
20
|
+
Term::ANSIColor.coloring = old_coloring
|
17
21
|
end
|
18
|
-
ensure
|
19
|
-
Term::ANSIColor.coloring = old_coloring
|
20
|
-
end
|
21
22
|
|
22
|
-
|
23
|
+
private
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
def colorize(key, value, string = key)
|
26
|
+
case style = cc.log.styles[key]
|
27
|
+
when nil, String, Array
|
28
|
+
apply_style(style, string)
|
29
|
+
when ComplexConfig::Settings
|
30
|
+
apply_style(style[value], string)
|
31
|
+
end
|
30
32
|
end
|
31
|
-
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
34
|
+
def apply_style(style, string)
|
35
|
+
style.nil? and return string + Term::ANSIColor.reset
|
36
|
+
string = Term::ANSIColor.uncolor(string)
|
37
|
+
if style.respond_to?(:each)
|
38
|
+
style.
|
39
|
+
each.
|
40
|
+
map { |s| -> v { __send__(:color, s, v) } }.
|
41
|
+
inject(string) { |v, s| s.(v) }
|
42
|
+
else
|
43
|
+
__send__(:color, style, string)
|
44
|
+
end
|
43
45
|
end
|
44
|
-
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
47
|
+
def format_pattern(format:)
|
48
|
+
format.
|
49
|
+
gsub('\n', "\n").
|
50
|
+
gsub('\t', "\t").
|
51
|
+
gsub(/\{(-)?(%[^%]+%)?([^}]+)\}/) {
|
52
|
+
invisible = $1.full?
|
53
|
+
directive = $2
|
54
|
+
key = $3
|
55
|
+
if value = @event[key]
|
56
|
+
formatted_value =
|
57
|
+
if directive
|
58
|
+
case directive
|
59
|
+
when /\A%O%/
|
60
|
+
format_object(value)
|
61
|
+
when /\A%([ulif])?t%/
|
62
|
+
flag = $1
|
63
|
+
t = case
|
64
|
+
when v = value.ask_and_send(:to_str)
|
65
|
+
Time.parse(v)
|
66
|
+
when v = value.ask_and_send(:to_time)
|
67
|
+
v
|
68
|
+
else
|
69
|
+
Time.at(0)
|
70
|
+
end
|
71
|
+
case flag
|
72
|
+
when ?u then t.utc.iso8601(3)
|
73
|
+
when ?l then t.localtime.iso8601(3)
|
74
|
+
when ?i then t.to_i.to_s
|
75
|
+
when ?f then t.to_f.to_s
|
76
|
+
else t.utc.iso8601(3)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
begin
|
80
|
+
directive[0..-2] % value
|
81
|
+
rescue ArgumentError
|
82
|
+
value.to_s
|
83
|
+
end
|
76
84
|
end
|
77
85
|
else
|
78
|
-
|
79
|
-
directive[0..-2] % value
|
80
|
-
rescue ArgumentError
|
81
|
-
value.to_s
|
82
|
-
end
|
86
|
+
value.to_s
|
83
87
|
end
|
84
|
-
|
85
|
-
|
88
|
+
colorize(key, value, formatted_value)
|
89
|
+
else
|
90
|
+
unless invisible
|
91
|
+
"{#{key}}"
|
86
92
|
end
|
87
|
-
colorize(key, value, formatted_value)
|
88
|
-
else
|
89
|
-
unless invisible
|
90
|
-
"{#{key}}"
|
91
93
|
end
|
92
|
-
|
93
|
-
|
94
|
-
end
|
94
|
+
}
|
95
|
+
end
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
97
|
+
def format_object(object, depth: 0, nl: ?\n)
|
98
|
+
case
|
99
|
+
when a = object.ask_and_send(:to_ary)
|
100
|
+
result = ''
|
101
|
+
depth += 2
|
102
|
+
for v in a
|
103
|
+
result << "\n#{' ' * depth}- #{format_object(v, depth: depth)}"
|
104
|
+
end
|
105
|
+
result
|
106
|
+
when h = object.ask_and_send(:to_hash)
|
107
|
+
result = ''
|
108
|
+
depth += 2
|
109
|
+
for k, v in h
|
110
|
+
result << "\n#{' ' * depth}#{k}: #{format_object(v, depth: depth)}"
|
111
|
+
end
|
112
|
+
result
|
113
|
+
else
|
114
|
+
object.to_s
|
110
115
|
end
|
111
|
-
result
|
112
|
-
else
|
113
|
-
object.to_s
|
114
116
|
end
|
115
117
|
end
|
116
118
|
end
|
@@ -1,49 +1,51 @@
|
|
1
|
-
|
2
|
-
class
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
module Betterlog
|
2
|
+
class Log
|
3
|
+
class Severity
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name.to_s.downcase.to_sym
|
8
|
+
begin
|
9
|
+
@level = Logger::Severity.const_get(@name.upcase)
|
10
|
+
rescue NameError
|
11
|
+
@name = :UNKNOWN
|
12
|
+
@level = Logger::Severity::UNKNOWN
|
13
|
+
end
|
12
14
|
end
|
13
|
-
end
|
14
15
|
|
15
|
-
|
16
|
-
|
17
|
-
|
16
|
+
def self.all
|
17
|
+
@all_constants ||= Logger::Severity.constants.map { |c| new(c) }
|
18
|
+
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
def to_i
|
21
|
+
@level
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
def to_s
|
25
|
+
@name.to_s.upcase
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
28
|
+
def to_sym
|
29
|
+
@name
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
def as_json(*)
|
33
|
+
to_sym
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
def <=>(other)
|
37
|
+
to_i <=> other.to_i
|
38
|
+
end
|
38
39
|
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
def eql?(other)
|
41
|
+
to_sym == other.to_sym
|
42
|
+
end
|
42
43
|
|
43
|
-
|
44
|
+
alias == eql?
|
44
45
|
|
45
|
-
|
46
|
-
|
46
|
+
def hash
|
47
|
+
@name.hash
|
48
|
+
end
|
47
49
|
end
|
48
50
|
end
|
49
51
|
end
|