betterlog 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|