semlogr 0.1.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 +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.rubocop.yml +17 -0
- data/Gemfile +3 -0
- data/LICENSE.md +21 -0
- data/README.md +50 -0
- data/Rakefile +8 -0
- data/bin/console +7 -0
- data/bin/setup +7 -0
- data/lib/semlogr/enrichers/host.rb +11 -0
- data/lib/semlogr/enrichers/property.rb +13 -0
- data/lib/semlogr/enrichers/thread.rb +9 -0
- data/lib/semlogr/events/log_event.rb +39 -0
- data/lib/semlogr/formatters/json_formatter.rb +41 -0
- data/lib/semlogr/formatters/property_value_formatter.rb +28 -0
- data/lib/semlogr/formatters/text_formatter.rb +35 -0
- data/lib/semlogr/log_severity.rb +26 -0
- data/lib/semlogr/logger.rb +109 -0
- data/lib/semlogr/logger_configuration.rb +40 -0
- data/lib/semlogr/properties/output_properties.rb +16 -0
- data/lib/semlogr/sinks/colored_console.rb +88 -0
- data/lib/semlogr/sinks/console.rb +16 -0
- data/lib/semlogr/sinks/file.rb +17 -0
- data/lib/semlogr/templates/parser.rb +79 -0
- data/lib/semlogr/templates/property_token.rb +39 -0
- data/lib/semlogr/templates/template.rb +21 -0
- data/lib/semlogr/templates/template_cache.rb +19 -0
- data/lib/semlogr/templates/text_token.rb +32 -0
- data/lib/semlogr/version.rb +3 -0
- data/lib/semlogr.rb +39 -0
- data/output.html +0 -0
- data/samples/basic.rb +64 -0
- data/semlogr.gemspec +30 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 44319632b673642da11033922b58b637648dec0d
|
4
|
+
data.tar.gz: 8a126de2e979a82ea257ded4e2f1656aed3ee6c8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 57772d694203303b49127e2fb37761828a34c8b7562cc1637b00fd9c7daa7e0ee15842c88a697cb088d04a6d9cdb5630c26c1cd379a1fbac2009db218049aafc
|
7
|
+
data.tar.gz: 699347ab3818596b392c6cfa1a4fc4849cefc48580bd1e54424a8c6f2046ef4ad6661fc969bd4e017804ef7bfbd6ce5de618d4af449539a9638c7b65150efa42
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.2
|
3
|
+
|
4
|
+
Documentation:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Metrics/AbcSize:
|
8
|
+
Max: 24
|
9
|
+
Metrics/CyclomaticComplexity:
|
10
|
+
Max: 8
|
11
|
+
Metrics/LineLength:
|
12
|
+
Max: 120
|
13
|
+
Metrics/MethodLength:
|
14
|
+
Max: 20
|
15
|
+
|
16
|
+
Style/MultilineMethodCallIndentation:
|
17
|
+
EnforcedStyle: indented
|
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 Semlogr
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+

|
2
|
+
|
3
|
+
# Semlogr
|
4
|
+
|
5
|
+
Semlogr is a semantic logger for Ruby inspired by the amazing semantic logger for .NET [Serilog](http://serilog.net/).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
To install:
|
10
|
+
|
11
|
+
gem install semlogr
|
12
|
+
|
13
|
+
Or if using bundler, add semlogr to your Gemfile:
|
14
|
+
|
15
|
+
gem 'semlogr'
|
16
|
+
|
17
|
+
then:
|
18
|
+
|
19
|
+
bundle install
|
20
|
+
|
21
|
+
## Getting Started
|
22
|
+
|
23
|
+
Create an instance of the logger configuring one or more sinks.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
require "semlogr"
|
27
|
+
require "semlogr/sinks/colored_console"
|
28
|
+
|
29
|
+
logger = Semlogr::Logger.create do |c|
|
30
|
+
c.log_at(Semlogr::LogSeverity::INFO)
|
31
|
+
|
32
|
+
c.write_to(Semlogr::Sinks::ColoredConsole.new)
|
33
|
+
end
|
34
|
+
|
35
|
+
logger.info('Customer {customer_id} did something interesting', customer_id: 1234)
|
36
|
+
```
|
37
|
+
|
38
|
+
## Development
|
39
|
+
|
40
|
+
After cloning the repository run `bundle install` to get up and running, to run the specs just run `rake spec`. You can also experiment in an interactive pry console using `bin/console`.
|
41
|
+
|
42
|
+
## Changes
|
43
|
+
|
44
|
+
### 0.1.0
|
45
|
+
|
46
|
+
- Initial commit, long long way to go :)!
|
47
|
+
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
See anything broken or something you would like to improve? feel free to submit an issue or better yet a pull request!
|
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module Semlogr
|
2
|
+
module Events
|
3
|
+
class LogEvent
|
4
|
+
attr_reader :severity
|
5
|
+
attr_reader :template
|
6
|
+
attr_reader :error
|
7
|
+
attr_reader :properties
|
8
|
+
attr_reader :timestamp
|
9
|
+
|
10
|
+
def initialize(severity, template, error, properties)
|
11
|
+
@timestamp = Time.now.utc
|
12
|
+
@severity = severity
|
13
|
+
@template = template
|
14
|
+
@error = error
|
15
|
+
@properties = properties
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_property(name)
|
19
|
+
@properties[name]
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_property(properties)
|
23
|
+
@properties.merge!(properties)
|
24
|
+
end
|
25
|
+
|
26
|
+
def render(output)
|
27
|
+
@template.render(output, @properties)
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
output = ''
|
32
|
+
|
33
|
+
render(output)
|
34
|
+
|
35
|
+
output
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Semlogr
|
4
|
+
module Formatters
|
5
|
+
class JsonFormatter
|
6
|
+
def format(log_event)
|
7
|
+
event = {
|
8
|
+
timestamp: log_event.timestamp.iso8601(3),
|
9
|
+
severity: log_event.severity,
|
10
|
+
message: log_event.to_s
|
11
|
+
}
|
12
|
+
|
13
|
+
add_error(event, log_event.error)
|
14
|
+
add_properties(event, log_event.properties)
|
15
|
+
|
16
|
+
yield(event) if block_given?
|
17
|
+
|
18
|
+
"#{event.to_json}\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def add_error(event, error)
|
24
|
+
return unless error
|
25
|
+
|
26
|
+
backtrace = error.backtrace || []
|
27
|
+
event[:error] = {
|
28
|
+
type: error.class,
|
29
|
+
message: error.message,
|
30
|
+
backtrace: backtrace.join("\n")
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_properties(event, properties)
|
35
|
+
return unless properties.any?
|
36
|
+
|
37
|
+
event[:properties] = properties
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Semlogr
|
2
|
+
module Formatters
|
3
|
+
class PropertyValueFormatter
|
4
|
+
QUOTE = '"'.freeze
|
5
|
+
|
6
|
+
def self.format(output, property_value)
|
7
|
+
case property_value
|
8
|
+
when nil
|
9
|
+
output << '(nil)'.freeze
|
10
|
+
when String
|
11
|
+
output << QUOTE
|
12
|
+
output << property_value
|
13
|
+
output << QUOTE
|
14
|
+
when StandardError
|
15
|
+
output << "#{property_value.class}: #{property_value.message}"
|
16
|
+
|
17
|
+
if property_value.backtrace
|
18
|
+
output << "\n\s\s#{property_value.backtrace.join("\n\s\s")}"
|
19
|
+
end
|
20
|
+
|
21
|
+
output << "\n"
|
22
|
+
else
|
23
|
+
output << property_value.to_s
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'semlogr/templates/parser'
|
2
|
+
require 'semlogr/properties/output_properties'
|
3
|
+
require 'semlogr/templates/property_token'
|
4
|
+
|
5
|
+
module Semlogr
|
6
|
+
module Formatters
|
7
|
+
class TextFormatter
|
8
|
+
DEFAULT_TEMPLATE = "[{timestamp}] {severity}: {message}\n{error}".freeze
|
9
|
+
|
10
|
+
def initialize(template: DEFAULT_TEMPLATE)
|
11
|
+
@template = Templates::Parser.parse(template)
|
12
|
+
end
|
13
|
+
|
14
|
+
def format(log_event)
|
15
|
+
output = ''
|
16
|
+
output_properties = Properties::OutputProperties.create(log_event)
|
17
|
+
|
18
|
+
@template.tokens.each do |token|
|
19
|
+
case token
|
20
|
+
when Templates::PropertyToken
|
21
|
+
if token.property_name == :message
|
22
|
+
log_event.render(output)
|
23
|
+
elsif output_properties[token.property_name]
|
24
|
+
token.render(output, output_properties)
|
25
|
+
end
|
26
|
+
else
|
27
|
+
token.render(output, output_properties)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
output
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Semlogr
|
2
|
+
class LogSeverity
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
def initialize(value, display_string)
|
8
|
+
@value = value
|
9
|
+
@display_string = display_string
|
10
|
+
end
|
11
|
+
|
12
|
+
def <=>(other)
|
13
|
+
@value <=> other.value
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
@display_string
|
18
|
+
end
|
19
|
+
|
20
|
+
DEBUG = LogSeverity.new(::Logger::DEBUG, 'DEBUG')
|
21
|
+
INFO = LogSeverity.new(::Logger::INFO, 'INFO')
|
22
|
+
WARN = LogSeverity.new(::Logger::WARN, 'WARN')
|
23
|
+
ERROR = LogSeverity.new(::Logger::ERROR, 'ERROR')
|
24
|
+
FATAL = LogSeverity.new(::Logger::FATAL, 'FATAL')
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'semlogr/logger_configuration'
|
3
|
+
require 'semlogr/log_severity'
|
4
|
+
require 'semlogr/events/log_event'
|
5
|
+
require 'semlogr/templates/parser'
|
6
|
+
require 'semlogr/enrichers/property'
|
7
|
+
|
8
|
+
module Semlogr
|
9
|
+
class Logger
|
10
|
+
def initialize(min_severity, enrichers, filters, sinks)
|
11
|
+
@min_severity = min_severity
|
12
|
+
@filters = filters
|
13
|
+
@enrichers = enrichers
|
14
|
+
@sinks = sinks
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.create
|
18
|
+
config = LoggerConfiguration.new
|
19
|
+
yield(config)
|
20
|
+
|
21
|
+
config.create_logger
|
22
|
+
end
|
23
|
+
|
24
|
+
def debug?
|
25
|
+
@min_severity <= LogSeverity::DEBUG
|
26
|
+
end
|
27
|
+
|
28
|
+
def info?
|
29
|
+
@min_severity <= LogSeverity::INFO
|
30
|
+
end
|
31
|
+
|
32
|
+
def warn?
|
33
|
+
@min_severity <= LogSeverity::WARN
|
34
|
+
end
|
35
|
+
|
36
|
+
def error?
|
37
|
+
@min_severity <= LogSeverity::ERROR
|
38
|
+
end
|
39
|
+
|
40
|
+
def fatal?
|
41
|
+
@min_severity <= LogSeverity::FATAL
|
42
|
+
end
|
43
|
+
|
44
|
+
def debug(template = nil, error: nil, **properties, &block)
|
45
|
+
log(LogSeverity::DEBUG, template, error, properties, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def info(template = nil, error: nil, **properties, &block)
|
49
|
+
log(LogSeverity::INFO, template, error, properties, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def warn(template = nil, error: nil, **properties, &block)
|
53
|
+
log(LogSeverity::WARN, template, error, properties, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
def error(template = nil, error: nil, **properties, &block)
|
57
|
+
log(LogSeverity::ERROR, template, error, properties, &block)
|
58
|
+
end
|
59
|
+
|
60
|
+
def fatal(template = nil, error: nil, **properties, &block)
|
61
|
+
log(LogSeverity::FATAL, template, error, properties, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def with_context(**properties)
|
65
|
+
Logger.new(
|
66
|
+
@min_severity,
|
67
|
+
@enrichers + [Enrichers::Property.new(properties)],
|
68
|
+
@filters,
|
69
|
+
@sinks
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def log(severity, template, error, properties, &block)
|
76
|
+
return true if @sinks.size.zero?
|
77
|
+
return true if severity < @min_severity
|
78
|
+
|
79
|
+
if template.nil? && block
|
80
|
+
template, properties = yield
|
81
|
+
|
82
|
+
properties ||= {}
|
83
|
+
error = properties[:error]
|
84
|
+
end
|
85
|
+
|
86
|
+
log_event = create_log_event(severity, template, error, properties)
|
87
|
+
|
88
|
+
@filters.each do |filter|
|
89
|
+
return false if filter.call(log_event)
|
90
|
+
end
|
91
|
+
|
92
|
+
@enrichers.each do |enricher|
|
93
|
+
enricher.enrich(log_event)
|
94
|
+
end
|
95
|
+
|
96
|
+
@sinks.each do |sink|
|
97
|
+
sink.emit(log_event)
|
98
|
+
end
|
99
|
+
|
100
|
+
true
|
101
|
+
end
|
102
|
+
|
103
|
+
def create_log_event(severity, template, error, properties)
|
104
|
+
template = Templates::Parser.parse(template)
|
105
|
+
|
106
|
+
Events::LogEvent.new(severity, template, error, properties)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Semlogr
|
2
|
+
class LoggerConfiguration
|
3
|
+
attr_reader :min_severity
|
4
|
+
attr_reader :enrichers
|
5
|
+
attr_reader :filters
|
6
|
+
attr_reader :sinks
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@min_severity = LogSeverity::DEBUG
|
10
|
+
@enrichers = []
|
11
|
+
@filters = []
|
12
|
+
@sinks = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def log_at(severity)
|
16
|
+
@min_severity = severity
|
17
|
+
end
|
18
|
+
|
19
|
+
def filter_when(filter)
|
20
|
+
@filters << filter
|
21
|
+
end
|
22
|
+
|
23
|
+
def enrich_with(enricher)
|
24
|
+
@enrichers << enricher
|
25
|
+
end
|
26
|
+
|
27
|
+
def write_to(sink)
|
28
|
+
@sinks << sink
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_logger
|
32
|
+
Logger.new(
|
33
|
+
@min_severity,
|
34
|
+
@enrichers,
|
35
|
+
@filters,
|
36
|
+
@sinks
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Semlogr
|
2
|
+
module Properties
|
3
|
+
class OutputProperties
|
4
|
+
def self.create(log_event)
|
5
|
+
properties = log_event.properties.merge(
|
6
|
+
timestamp: log_event.timestamp,
|
7
|
+
severity: log_event.severity
|
8
|
+
)
|
9
|
+
|
10
|
+
properties[:error] = log_event.error if log_event.error
|
11
|
+
|
12
|
+
properties
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'semlogr/sinks/console'
|
2
|
+
require 'semlogr/properties/output_properties'
|
3
|
+
|
4
|
+
module Semlogr
|
5
|
+
module Sinks
|
6
|
+
class ColoredConsole
|
7
|
+
DEFAULT_TEMPLATE = "[{timestamp}] {severity}: {message}\n{error}".freeze
|
8
|
+
|
9
|
+
LOG_SEVERITY_COLORS = {
|
10
|
+
LogSeverity::DEBUG => :white,
|
11
|
+
LogSeverity::INFO => :white,
|
12
|
+
LogSeverity::WARN => :yellow,
|
13
|
+
LogSeverity::ERROR => :red,
|
14
|
+
LogSeverity::FATAL => :red
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
COLOR_CODES = {
|
18
|
+
white: 37,
|
19
|
+
yellow: 33,
|
20
|
+
red: 31,
|
21
|
+
blue: 34
|
22
|
+
}.freeze
|
23
|
+
|
24
|
+
def initialize(template: DEFAULT_TEMPLATE)
|
25
|
+
@template = Templates::Parser.parse(template)
|
26
|
+
end
|
27
|
+
|
28
|
+
def emit(log_event)
|
29
|
+
output = ''
|
30
|
+
output_properties = Properties::OutputProperties.create(log_event)
|
31
|
+
|
32
|
+
@template.tokens.each do |token|
|
33
|
+
case token
|
34
|
+
when Templates::PropertyToken
|
35
|
+
render_property_token(output, token, log_event, output_properties)
|
36
|
+
else
|
37
|
+
token.render(output, output_properties)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
STDOUT.write(output)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def render_property_token(output, token, log_event, output_properties)
|
47
|
+
case token.property_name
|
48
|
+
when :message
|
49
|
+
render_message(output, log_event)
|
50
|
+
when :severity
|
51
|
+
color = LOG_SEVERITY_COLORS[log_event.severity] || :white
|
52
|
+
colorize(output, color) do
|
53
|
+
token.render(output, output_properties)
|
54
|
+
end
|
55
|
+
when :error
|
56
|
+
return unless output_properties[:error]
|
57
|
+
|
58
|
+
colorize(output, :red) do
|
59
|
+
token.render(output, output_properties)
|
60
|
+
end
|
61
|
+
else
|
62
|
+
return unless output_properties[token.property_name]
|
63
|
+
|
64
|
+
token.render(output, output_properties)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def render_message(output, log_event)
|
69
|
+
log_event.template.tokens.each do |token|
|
70
|
+
case token
|
71
|
+
when Templates::PropertyToken
|
72
|
+
colorize(output, :blue) do
|
73
|
+
token.render(output, log_event.properties)
|
74
|
+
end
|
75
|
+
else
|
76
|
+
token.render(output, log_event.properties)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def colorize(output, color)
|
82
|
+
output << "\e[#{COLOR_CODES[color]}m"
|
83
|
+
yield
|
84
|
+
output << "\e[0m"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'semlogr/formatters/text_formatter'
|
2
|
+
|
3
|
+
module Semlogr
|
4
|
+
module Sinks
|
5
|
+
class Console
|
6
|
+
def initialize(formatter: nil)
|
7
|
+
@formatter = formatter || Formatters::TextFormatter.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def emit(log_event)
|
11
|
+
output = @formatter.format(log_event)
|
12
|
+
STDOUT.write(output)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'semlogr/formatters/text_formatter'
|
2
|
+
|
3
|
+
module Semlogr
|
4
|
+
module Sinks
|
5
|
+
class File
|
6
|
+
def initialize(file, shift_age: nil, shift_size: nil, formatter: nil)
|
7
|
+
@logdev = ::Logger::LogDevice.new(file, shift_age: shift_age, shift_size: shift_size)
|
8
|
+
@formatter = formatter || Formatters::TextFormatter.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def emit(log_event)
|
12
|
+
output = @formatter.format(log_event)
|
13
|
+
@logdev.write(output)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'semlogr/templates/template'
|
2
|
+
require 'semlogr/templates/text_token'
|
3
|
+
require 'semlogr/templates/property_token'
|
4
|
+
require 'semlogr/templates/template_cache'
|
5
|
+
|
6
|
+
module Semlogr
|
7
|
+
module Templates
|
8
|
+
class Parser
|
9
|
+
@template_cache = TemplateCache.new(1000)
|
10
|
+
|
11
|
+
PROPERTY_TOKEN_START = '{'.freeze
|
12
|
+
PROPERTY_TOKEN_END = '}'.freeze
|
13
|
+
|
14
|
+
def self.parse(template)
|
15
|
+
return Template::EMPTY unless template && !template.empty?
|
16
|
+
|
17
|
+
cached_template = @template_cache[template]
|
18
|
+
return cached_template if cached_template
|
19
|
+
|
20
|
+
tokens = []
|
21
|
+
pos = 0
|
22
|
+
|
23
|
+
while pos < template.size
|
24
|
+
text_token, pos = parse_text_token(template, pos)
|
25
|
+
tokens.push(text_token) if text_token
|
26
|
+
|
27
|
+
property_token, pos = parse_property_token(template, pos)
|
28
|
+
tokens.push(property_token) if property_token
|
29
|
+
end
|
30
|
+
|
31
|
+
@template_cache[template] = Template.new(tokens)
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.parse_text_token(template, start)
|
35
|
+
token = nil
|
36
|
+
pos = start
|
37
|
+
|
38
|
+
while pos < template.size
|
39
|
+
break if template[pos] == PROPERTY_TOKEN_START
|
40
|
+
|
41
|
+
pos += 1
|
42
|
+
end
|
43
|
+
|
44
|
+
if pos > start
|
45
|
+
text = template[start..pos - 1]
|
46
|
+
token = TextToken.new(text)
|
47
|
+
end
|
48
|
+
|
49
|
+
[token, pos]
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.parse_property_token(template, start)
|
53
|
+
return [nil, start] unless template[start] == PROPERTY_TOKEN_START
|
54
|
+
|
55
|
+
token = nil
|
56
|
+
pos = start
|
57
|
+
|
58
|
+
while pos < template.size
|
59
|
+
if template[pos] == PROPERTY_TOKEN_END
|
60
|
+
raw_text = template[start..pos]
|
61
|
+
property_name = raw_text[1..-2]
|
62
|
+
token = PropertyToken.new(raw_text, property_name.to_sym)
|
63
|
+
|
64
|
+
return [token, pos + 1]
|
65
|
+
end
|
66
|
+
|
67
|
+
pos += 1
|
68
|
+
end
|
69
|
+
|
70
|
+
if pos > start
|
71
|
+
text = template[start..pos - 1]
|
72
|
+
token = TextToken.new(text)
|
73
|
+
end
|
74
|
+
|
75
|
+
[token, pos]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'semlogr/formatters/property_value_formatter'
|
2
|
+
|
3
|
+
module Semlogr
|
4
|
+
module Templates
|
5
|
+
class PropertyToken
|
6
|
+
attr_accessor :property_name
|
7
|
+
|
8
|
+
def initialize(raw_text, property_name)
|
9
|
+
@raw_text = raw_text
|
10
|
+
@property_name = property_name
|
11
|
+
end
|
12
|
+
|
13
|
+
def render(output, properties)
|
14
|
+
if properties.key?(@property_name)
|
15
|
+
property_value = properties[@property_name]
|
16
|
+
|
17
|
+
Formatters::PropertyValueFormatter.format(output, property_value)
|
18
|
+
else
|
19
|
+
output << @raw_text
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def ==(other)
|
24
|
+
return false unless other
|
25
|
+
return false unless other.respond_to?(:property_name)
|
26
|
+
|
27
|
+
@property_name == other.property_name
|
28
|
+
end
|
29
|
+
|
30
|
+
def eql?(other)
|
31
|
+
self == other
|
32
|
+
end
|
33
|
+
|
34
|
+
def hash
|
35
|
+
@property_name.hash
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'semlogr/templates/text_token'
|
2
|
+
|
3
|
+
module Semlogr
|
4
|
+
module Templates
|
5
|
+
class Template
|
6
|
+
attr_accessor :tokens
|
7
|
+
|
8
|
+
def initialize(tokens)
|
9
|
+
@tokens = tokens
|
10
|
+
end
|
11
|
+
|
12
|
+
def render(output, properties)
|
13
|
+
@tokens.each do |token|
|
14
|
+
token.render(output, properties)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
EMPTY = Template.new([TextToken::EMPTY])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'lru_redux'
|
2
|
+
|
3
|
+
module Semlogr
|
4
|
+
module Templates
|
5
|
+
class TemplateCache
|
6
|
+
def initialize(max_size)
|
7
|
+
@template_cache = LruRedux::ThreadSafeCache.new(max_size)
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](key)
|
11
|
+
@template_cache[key]
|
12
|
+
end
|
13
|
+
|
14
|
+
def []=(key, value)
|
15
|
+
@template_cache[key] = value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Semlogr
|
2
|
+
module Templates
|
3
|
+
class TextToken
|
4
|
+
attr_accessor :text
|
5
|
+
|
6
|
+
def initialize(text)
|
7
|
+
@text = text
|
8
|
+
end
|
9
|
+
|
10
|
+
def render(output, _properties)
|
11
|
+
output << @text
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
return false unless other
|
16
|
+
return false unless other.respond_to?(:text)
|
17
|
+
|
18
|
+
@text == other.text
|
19
|
+
end
|
20
|
+
|
21
|
+
def eql?(other)
|
22
|
+
self == other
|
23
|
+
end
|
24
|
+
|
25
|
+
def hash
|
26
|
+
@text.hash
|
27
|
+
end
|
28
|
+
|
29
|
+
EMPTY = TextToken.new('')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/semlogr.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'semlogr/logger'
|
2
|
+
|
3
|
+
module Semlogr
|
4
|
+
@logger = nil
|
5
|
+
|
6
|
+
def self.configure
|
7
|
+
@logger = Logger.create do |config|
|
8
|
+
yield(config)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.logger
|
13
|
+
unless @logger
|
14
|
+
raise StandardError, 'You need to initialize the logger instance by calling Semlogr::Log.configure first!'
|
15
|
+
end
|
16
|
+
|
17
|
+
@logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.debug(template = nil, **properties, &block)
|
21
|
+
logger.debug(template, **properties, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.info(template = nil, **properties, &block)
|
25
|
+
logger.info(template, **properties, &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.warn(template = nil, **properties, &block)
|
29
|
+
logger.warn(template, **properties, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.error(template = nil, **properties, &block)
|
33
|
+
logger.error(template, **properties, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.fatal(template = nil, **properties, &block)
|
37
|
+
logger.fatal(template, **properties, &block)
|
38
|
+
end
|
39
|
+
end
|
data/output.html
ADDED
File without changes
|
data/samples/basic.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'semlogr'
|
3
|
+
require 'semlogr/sinks/console'
|
4
|
+
require 'semlogr/sinks/colored_console'
|
5
|
+
require 'semlogr/sinks/file'
|
6
|
+
require 'semlogr/formatters/json_formatter'
|
7
|
+
require 'semlogr/enrichers/thread'
|
8
|
+
require 'semlogr/enrichers/host'
|
9
|
+
require 'semlogr/enrichers/property'
|
10
|
+
|
11
|
+
logger = Semlogr::Logger.create do |c|
|
12
|
+
c.log_at Semlogr::LogSeverity::DEBUG
|
13
|
+
|
14
|
+
c.write_to Semlogr::Sinks::Console.new
|
15
|
+
c.write_to Semlogr::Sinks::ColoredConsole.new
|
16
|
+
c.write_to Semlogr::Sinks::Console.new(formatter: Semlogr::Formatters::JsonFormatter.new)
|
17
|
+
|
18
|
+
c.enrich_with Semlogr::Enrichers::Thread.new
|
19
|
+
c.enrich_with Semlogr::Enrichers::Host.new
|
20
|
+
c.enrich_with Semlogr::Enrichers::Property.new(version: '1.0')
|
21
|
+
|
22
|
+
c.filter_when lambda { |log_event|
|
23
|
+
log_event.get_property(:id) == 123
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
logger.debug('Test {id}, string {string}')
|
28
|
+
logger.debug('Test {id}, string {string}', id: nil, string: nil)
|
29
|
+
logger.debug('Test {id}, string {string}', id: 123, string: 'foo')
|
30
|
+
logger.debug('Test {id}, string {string}', id: 1234, string: 'foo')
|
31
|
+
logger.info('Test {id}, string {string}', id: 1234, string: 'foo')
|
32
|
+
logger.warn('Test {id}, string {string}', id: 1234, string: 'foo')
|
33
|
+
logger.fatal('Test {id}, string {string}', id: 1234, string: 'foo')
|
34
|
+
logger.fatal('Test array {array}', array: [1, 2, 3, 'foo'])
|
35
|
+
|
36
|
+
logger.debug do
|
37
|
+
'Testing with a block'
|
38
|
+
end
|
39
|
+
|
40
|
+
logger.debug do
|
41
|
+
['Testing with a block, id: {id}', id: 1234]
|
42
|
+
end
|
43
|
+
|
44
|
+
logger.error('ERROR!!!', error: StandardError.new('test'))
|
45
|
+
logger.error('ERROR!!!', error: StandardError.new('test'))
|
46
|
+
|
47
|
+
begin
|
48
|
+
def bob
|
49
|
+
foo
|
50
|
+
end
|
51
|
+
|
52
|
+
def foo
|
53
|
+
raise StandardError, 'foo'
|
54
|
+
end
|
55
|
+
|
56
|
+
bob
|
57
|
+
rescue => ex
|
58
|
+
logger.warn('Oops, id: {id}', id: 1234, error: ex)
|
59
|
+
logger.error('Oops, id: {id}', id: 1234, error: ex)
|
60
|
+
|
61
|
+
logger.error do
|
62
|
+
['Testing error with a block, id: {id}', error: ex, id: 1234]
|
63
|
+
end
|
64
|
+
end
|
data/semlogr.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require 'semlogr/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'semlogr'
|
9
|
+
spec.version = Semlogr::VERSION
|
10
|
+
spec.authors = ['Stefan Sedich']
|
11
|
+
spec.email = ['stefan.sedich@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = 'Semantic logging for Ruby'
|
14
|
+
spec.description = 'A modern semantic logger for Ruby inspired by Serilog.'
|
15
|
+
spec.homepage = 'https://github.com/semlogr/semlogr'
|
16
|
+
spec.license = 'MIT'
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.bindir = 'exe'
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_dependency 'lru_redux', '~> 1.1.0'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
26
|
+
spec.add_development_dependency 'pry', '~> 0.10.3'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
29
|
+
spec.add_development_dependency 'rubocop', '~> 0.43'
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: semlogr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stefan Sedich
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-10-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: lru_redux
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.1.0
|
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.12'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.12'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.10.3
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.10.3
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.43'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.43'
|
97
|
+
description: A modern semantic logger for Ruby inspired by Serilog.
|
98
|
+
email:
|
99
|
+
- stefan.sedich@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- ".rspec"
|
106
|
+
- ".rubocop.yml"
|
107
|
+
- Gemfile
|
108
|
+
- LICENSE.md
|
109
|
+
- README.md
|
110
|
+
- Rakefile
|
111
|
+
- bin/console
|
112
|
+
- bin/setup
|
113
|
+
- lib/semlogr.rb
|
114
|
+
- lib/semlogr/enrichers/host.rb
|
115
|
+
- lib/semlogr/enrichers/property.rb
|
116
|
+
- lib/semlogr/enrichers/thread.rb
|
117
|
+
- lib/semlogr/events/log_event.rb
|
118
|
+
- lib/semlogr/formatters/json_formatter.rb
|
119
|
+
- lib/semlogr/formatters/property_value_formatter.rb
|
120
|
+
- lib/semlogr/formatters/text_formatter.rb
|
121
|
+
- lib/semlogr/log_severity.rb
|
122
|
+
- lib/semlogr/logger.rb
|
123
|
+
- lib/semlogr/logger_configuration.rb
|
124
|
+
- lib/semlogr/properties/output_properties.rb
|
125
|
+
- lib/semlogr/sinks/colored_console.rb
|
126
|
+
- lib/semlogr/sinks/console.rb
|
127
|
+
- lib/semlogr/sinks/file.rb
|
128
|
+
- lib/semlogr/templates/parser.rb
|
129
|
+
- lib/semlogr/templates/property_token.rb
|
130
|
+
- lib/semlogr/templates/template.rb
|
131
|
+
- lib/semlogr/templates/template_cache.rb
|
132
|
+
- lib/semlogr/templates/text_token.rb
|
133
|
+
- lib/semlogr/version.rb
|
134
|
+
- output.html
|
135
|
+
- samples/basic.rb
|
136
|
+
- semlogr.gemspec
|
137
|
+
homepage: https://github.com/semlogr/semlogr
|
138
|
+
licenses:
|
139
|
+
- MIT
|
140
|
+
metadata: {}
|
141
|
+
post_install_message:
|
142
|
+
rdoc_options: []
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: '0'
|
155
|
+
requirements: []
|
156
|
+
rubyforge_project:
|
157
|
+
rubygems_version: 2.5.1
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Semantic logging for Ruby
|
161
|
+
test_files: []
|