chalk-log 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +8 -0
- data/History.txt +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +142 -0
- data/Rakefile +17 -0
- data/chalk-log.gemspec +26 -0
- data/config.yaml +22 -0
- data/lib/chalk-log.rb +137 -0
- data/lib/chalk-log/errors.rb +7 -0
- data/lib/chalk-log/layout.rb +192 -0
- data/lib/chalk-log/logger.rb +63 -0
- data/lib/chalk-log/utils.rb +84 -0
- data/lib/chalk-log/version.rb +5 -0
- data/tddium.yml +8 -0
- data/test/_lib.rb +16 -0
- data/test/_lib/fake.rb +5 -0
- data/test/_lib/fake/event.rb +9 -0
- data/test/functional/_lib.rb +10 -0
- data/test/functional/formatting.rb +188 -0
- data/test/functional/log.rb +296 -0
- data/test/unit/_lib.rb +10 -0
- data/test/unit/chalk-log/utils.rb +13 -0
- metadata +195 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/History.txt
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
=== 0.1.0 2014-05-25
|
2
|
+
|
3
|
+
* Started being more strict with arguments passed. In particular,
|
4
|
+
stopped accepting nil or boolean trailing arguments.
|
5
|
+
* Switched to using Chalk::Config, eliminating Chalk::Log::Config.
|
6
|
+
* You now disable Chalk::Log by setting either of `configatron.chalk.log.disabled || LSpace[:'chalk.log.disabled']`. `ENV["CHALK_NOLOG"]` and `LSpace[:logging_disabled] are now ignored.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Stripe
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# Chalk::Log
|
2
|
+
|
3
|
+
`Chalk::Log` adds a logger object to any class, which can be used for
|
4
|
+
unstructured or semi-structured logging. Use it as follows:
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
class A
|
8
|
+
include Chalk::Log
|
9
|
+
end
|
10
|
+
|
11
|
+
A.log.info('hello', key: 'value')
|
12
|
+
#=> [2013-06-18 22:18:28.314756] [64682] hello: key="value"
|
13
|
+
```
|
14
|
+
|
15
|
+
The output is both human-digestable and easily parsed by log indexing
|
16
|
+
systems such as [Splunk](http://www.splunk.com/) or
|
17
|
+
[Logstash](http://logstash.net/).
|
18
|
+
|
19
|
+
It can also pretty-print exceptions for you:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
module A; include Chalk::Log; end
|
23
|
+
begin; raise "hi"; rescue => e; end
|
24
|
+
A.log.error('Something went wrong', e)
|
25
|
+
#=> Something went wrong: hi (RuntimeError)
|
26
|
+
# (irb):8:in `irb_binding'
|
27
|
+
# /Users/gdb/.rbenv/versions/1.9.3-p362/lib/ruby/1.9.1/irb/workspace.rb:80:in `eval# /Users/gdb/.rbenv/versions/1.9.3-p362/lib/ruby/1.9.1/irb/workspace.rb:80:in `evaluate'
|
28
|
+
# /Users/gdb/.rbenv/versions/1.9.3-p362/lib/ruby/1.9.1/irb/context.rb:254:in `evaluate'
|
29
|
+
# /Users/gdb/.rbenv/versions/1.9.3-p362/lib/ruby/1.9.1/irb.rb:159:in `block (2 levels) in eval_input'
|
30
|
+
# [...]
|
31
|
+
```
|
32
|
+
|
33
|
+
The log methods accept a message and/or an exception and/or an info
|
34
|
+
hash (if multiple are passed, they must be provided in that
|
35
|
+
order). The log methods will never throw an exception, but will
|
36
|
+
instead print an log message indicating they had a fault.
|
37
|
+
|
38
|
+
## Overview
|
39
|
+
|
40
|
+
Including `Chalk::Log` creates a `log` method as both a class an
|
41
|
+
instance method, which returns a class-specific logger.
|
42
|
+
|
43
|
+
By default, it tags loglines with auxiliary information: a
|
44
|
+
microsecond-granularity timestamp, the PID, and an action_id (which
|
45
|
+
should tie together all lines for a single logical action in your
|
46
|
+
system, such as a web request).
|
47
|
+
|
48
|
+
You can turn off tagging, or just turn off timestamping, through
|
49
|
+
appropriate configatron settings (see [config.yaml](/config.yaml)).
|
50
|
+
|
51
|
+
There are also two `LSpace` dynamic settings available:
|
52
|
+
|
53
|
+
- `LSpace[:action_id]`: Set the action_id dynamically for this action. (This is used automatically by things like `Chalk::Web` which have a well-defined action.)
|
54
|
+
- `LSpace[:'chalk.log.disabled']`: Disable all logging.
|
55
|
+
|
56
|
+
You can use `LSpace` settings as follows:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class A; include Chalk::Log; end
|
60
|
+
foo = A.new
|
61
|
+
|
62
|
+
LSpace.with(action_id: 'request-123') do
|
63
|
+
foo.log.info('Test')
|
64
|
+
#=> [2014-05-26 01:12:28.485822] [47325|request-123] Test
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
68
|
+
## Log methods
|
69
|
+
|
70
|
+
`Chalk::Log` provides five log levels:
|
71
|
+
|
72
|
+
debug, info, warn, error, fatal
|
73
|
+
|
74
|
+
## Inheritance
|
75
|
+
|
76
|
+
`Chalk::Log` makes a heroic effort to ensure that inclusion chaining
|
77
|
+
works, so you can do things like:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
module A
|
81
|
+
include Chalk::Log
|
82
|
+
end
|
83
|
+
|
84
|
+
module B
|
85
|
+
include A
|
86
|
+
end
|
87
|
+
|
88
|
+
class C
|
89
|
+
include B
|
90
|
+
end
|
91
|
+
```
|
92
|
+
|
93
|
+
and still have `C.log` and `C.new.log` work. (Normally you'd expect
|
94
|
+
for the class-method version to be left behind.)
|
95
|
+
|
96
|
+
## Best practices
|
97
|
+
|
98
|
+
- You should never use string interpolation in your log
|
99
|
+
message. Instead, always use the structured logging keys. So for
|
100
|
+
example:
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
# Bad
|
104
|
+
log.info("Just printed #{lines.length} lines")
|
105
|
+
# Good
|
106
|
+
log.info("Printed", lines: lines.length)
|
107
|
+
```
|
108
|
+
|
109
|
+
- Don't end messages with a punctuation -- `Chalk::Log` will
|
110
|
+
automatically add a colon if an info hash is provided; if not, it's
|
111
|
+
fine to just end without trailing punctutaion. Case in point
|
112
|
+
|
113
|
+
- In most projects, you'll find most of your classes start including
|
114
|
+
`Chalk::Log` -- it's pretty cheap to add it, and it's quite
|
115
|
+
lightweight to use. (In contrast, there's no good way to autoinclude
|
116
|
+
it, since that would likely break many classes which aren't
|
117
|
+
expecting a magical `log` method to appear.)
|
118
|
+
|
119
|
+
## Limitations
|
120
|
+
|
121
|
+
`Chalk::Log` is not very configurable. Our usage at Stripe tends to be
|
122
|
+
fairly opinionated, so there hasn't been much demand for increased
|
123
|
+
configurability. We would be open to making it less rigid,
|
124
|
+
however. (In any case, under the hood `Chalk::Log` is just using the
|
125
|
+
`logging` gem, so if the need arises it wouldn't be hard to acquire
|
126
|
+
the full flexibility of `logging`.)
|
127
|
+
|
128
|
+
# Contributors
|
129
|
+
|
130
|
+
- Greg Brockman
|
131
|
+
- Andreas Fuchs
|
132
|
+
- Andy Brody
|
133
|
+
- Anurag Goel
|
134
|
+
- Evan Broder
|
135
|
+
- Nelson Elhage
|
136
|
+
- Brian Krausz
|
137
|
+
- Christian Anderson
|
138
|
+
- Jeff Balogh
|
139
|
+
- Jeremy Hoon
|
140
|
+
- Julia Evans
|
141
|
+
- Russell Davis
|
142
|
+
- Steven Noble
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'chalk-rake/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
task :default do
|
7
|
+
sh 'rake -T'
|
8
|
+
end
|
9
|
+
|
10
|
+
Rake::TestTask.new do |t|
|
11
|
+
t.libs = ["lib"]
|
12
|
+
# t.warning = true
|
13
|
+
t.verbose = true
|
14
|
+
t.test_files = FileList['test/**/*.rb'].reject do |file|
|
15
|
+
file.end_with?('_lib.rb') || file.include?('/_lib/')
|
16
|
+
end
|
17
|
+
end
|
data/chalk-log.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'chalk-log/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'chalk-log'
|
8
|
+
gem.version = Chalk::Log::VERSION
|
9
|
+
gem.authors = ['Stripe']
|
10
|
+
gem.email = ['oss@stripe.com']
|
11
|
+
gem.description = %q{Extends classes with a `log` method}
|
12
|
+
gem.summary = %q{Chalk::Log makes any class loggable. It provides a logger that can be used for both structured and unstructured log.}
|
13
|
+
gem.homepage = 'https://github.com/stripe/chalk-log'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ['lib']
|
19
|
+
gem.add_dependency 'chalk-config'
|
20
|
+
gem.add_dependency 'logging'
|
21
|
+
gem.add_dependency 'lspace'
|
22
|
+
gem.add_development_dependency 'rake'
|
23
|
+
gem.add_development_dependency 'minitest', '~> 3.2.0'
|
24
|
+
gem.add_development_dependency 'mocha'
|
25
|
+
gem.add_development_dependency 'chalk-rake'
|
26
|
+
end
|
data/config.yaml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
chalk:
|
2
|
+
log:
|
3
|
+
# The default log level
|
4
|
+
default_level: 'INFO'
|
5
|
+
|
6
|
+
# Whether to enable tagging (with timestamp, PID, action_id).
|
7
|
+
tagging: true
|
8
|
+
|
9
|
+
# Whether to enable tagging with PID (can be subsumed by
|
10
|
+
# `tagging: false`)
|
11
|
+
pid: true
|
12
|
+
|
13
|
+
# Whether to enable tagging with timestamp (can be subsumed by
|
14
|
+
# `tagging: false`). This option is set automatically in
|
15
|
+
# lib/chalk-log.rb.
|
16
|
+
timestamp: null
|
17
|
+
|
18
|
+
# Whether to show logs at all
|
19
|
+
disabled: false
|
20
|
+
|
21
|
+
# Whether to remove gem lines from backtraces
|
22
|
+
compress_backtraces: false
|
data/lib/chalk-log.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'logging'
|
2
|
+
require 'lspace'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'chalk-config'
|
6
|
+
require 'chalk-log/version'
|
7
|
+
|
8
|
+
# Include `Chalk::Log` in a class or module to make that class (and
|
9
|
+
# all subclasses / includees / extendees) loggable. This creates a
|
10
|
+
# class and instance `log` method which you can call from within your
|
11
|
+
# loggable class.
|
12
|
+
#
|
13
|
+
# Loggers are per-class and can be manipulated as you'd expect:
|
14
|
+
#
|
15
|
+
# ```ruby
|
16
|
+
# class A
|
17
|
+
# include Chalk::Log
|
18
|
+
#
|
19
|
+
# log.level = 'DEBUG'
|
20
|
+
# log.debug('Now you see me!')
|
21
|
+
# log.level = 'INFO'
|
22
|
+
# log.debug('Now you do not!')
|
23
|
+
# end
|
24
|
+
# ```
|
25
|
+
#
|
26
|
+
# You shouldn't need to directly access any of the methods on
|
27
|
+
# `Chalk::Log` itself.
|
28
|
+
module Chalk::Log
|
29
|
+
require 'chalk-log/errors'
|
30
|
+
require 'chalk-log/logger'
|
31
|
+
require 'chalk-log/layout'
|
32
|
+
require 'chalk-log/utils'
|
33
|
+
|
34
|
+
# The set of available log methods. (Changing these is not currently
|
35
|
+
# a supported interface, though if the need arises it'd be easy to
|
36
|
+
# add.)
|
37
|
+
LEVELS = [:debug, :info, :warn, :error, :fatal].freeze
|
38
|
+
|
39
|
+
@included = Set.new
|
40
|
+
|
41
|
+
# Method which goes through heroic efforts to ensure that the whole
|
42
|
+
# inclusion hierarchy has their `log` accessors.
|
43
|
+
def self.included(other)
|
44
|
+
if other == Object
|
45
|
+
raise "You have attempted to `include Chalk::Log` onto Object. This is disallowed, since otherwise it might shadow any `log` method on classes that weren't expecting it (including, for example, `configatron.chalk.log`)."
|
46
|
+
end
|
47
|
+
|
48
|
+
# Already been through this ordeal; no need to repeat it. (There
|
49
|
+
# shouldn't be any semantic harm to doing so, just a potential
|
50
|
+
# performance hit.)
|
51
|
+
return if @included.include?(other)
|
52
|
+
@included << other
|
53
|
+
|
54
|
+
# Make sure to define the .log class method
|
55
|
+
other.extend(ClassMethods)
|
56
|
+
|
57
|
+
# If it's a module, we need to make sure both inclusion/extension
|
58
|
+
# result in virally carrying Chalk::Log inclusion downstream.
|
59
|
+
if other.instance_of?(Module)
|
60
|
+
other.class_eval do
|
61
|
+
included = method(:included)
|
62
|
+
extended = method(:extended)
|
63
|
+
|
64
|
+
define_singleton_method(:included) do |other|
|
65
|
+
other.send(:include, Chalk::Log)
|
66
|
+
included.call(other)
|
67
|
+
end
|
68
|
+
|
69
|
+
define_singleton_method(:extended) do |other|
|
70
|
+
other.send(:include, Chalk::Log)
|
71
|
+
extended.call(other)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public-facing initialization method for all `Chalk::Log`
|
78
|
+
# state. Unlike most other Chalk initializers, this will be
|
79
|
+
# automatically run (invoked on first logger instantiation). It is
|
80
|
+
# idempotent.
|
81
|
+
def self.init
|
82
|
+
return if @init
|
83
|
+
@init = true
|
84
|
+
|
85
|
+
# Load relevant configatron stuff
|
86
|
+
Chalk::Config.register(File.expand_path('../../config.yaml', __FILE__),
|
87
|
+
raw: true)
|
88
|
+
|
89
|
+
# The assumption is you'll pipe your logs through something like
|
90
|
+
# [Unilog](https://github.com/stripe/unilog) in production, which
|
91
|
+
# does its own timestamping.
|
92
|
+
Chalk::Config.register_raw(chalk: {log: {timestamp: STDERR.tty?}})
|
93
|
+
|
94
|
+
::Logging.init(*LEVELS)
|
95
|
+
::Logging.logger.root.add_appenders(
|
96
|
+
::Logging.appenders.stderr(layout: layout)
|
97
|
+
)
|
98
|
+
|
99
|
+
Chalk::Log::Logger.init
|
100
|
+
end
|
101
|
+
|
102
|
+
# The default layout to use for the root `Logging::Logger`.
|
103
|
+
def self.layout
|
104
|
+
@layout ||= Chalk::Log::Layout.new
|
105
|
+
end
|
106
|
+
|
107
|
+
# Home of the backend `log` method people call; included *and*
|
108
|
+
# extended everywhere that includes Chalk::Log.
|
109
|
+
module ClassMethods
|
110
|
+
# The backend `log` method exposed to everyone. (In practice, the
|
111
|
+
# method people call directly is one wrapper above this.)
|
112
|
+
#
|
113
|
+
# Sets a `@__chalk_log` variable to hold the logger instance.
|
114
|
+
def log
|
115
|
+
@__chalk_log ||= Chalk::Log::Logger.new(self.name)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Make the `log` method inheritable.
|
120
|
+
include ClassMethods
|
121
|
+
|
122
|
+
# The technique here is a bit tricky. The same `log` implementation
|
123
|
+
# defined on any class needs to be callable by either an instance or
|
124
|
+
# class. (See the "correctly make the end class loggable when it has
|
125
|
+
# already included loggable" test for why. In particular, someone
|
126
|
+
# may have already included me, and then clobbered the class
|
127
|
+
# implementations by extending me.) Hence we do this "defer to
|
128
|
+
# class, unless I am a class" logic.
|
129
|
+
log = instance_method(:log)
|
130
|
+
define_method(:log) do
|
131
|
+
if self.kind_of?(Class)
|
132
|
+
log.bind(self).call
|
133
|
+
else
|
134
|
+
self.class.log
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'set'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
# The layout backend for the Logging::Logger.
|
6
|
+
#
|
7
|
+
# Accepts a message and/or an exception and/or an info
|
8
|
+
# hash (if multiple are passed, they must be provided in that
|
9
|
+
# order)
|
10
|
+
class Chalk::Log::Layout < ::Logging::Layout
|
11
|
+
# Formats an event, and makes a heroic effort to tell you if
|
12
|
+
# something went wrong. (Logging will otherwise silently swallow any
|
13
|
+
# exceptions that get thrown.)
|
14
|
+
#
|
15
|
+
# @param event provided by the Logging::Logger
|
16
|
+
def format(event)
|
17
|
+
begin
|
18
|
+
begin
|
19
|
+
begin
|
20
|
+
do_format(event)
|
21
|
+
rescue StandardError => e
|
22
|
+
# Single fault!
|
23
|
+
error!('[Chalk::Log fault: Could not format message] ', e)
|
24
|
+
end
|
25
|
+
rescue StandardError => e
|
26
|
+
# Double fault!
|
27
|
+
"[Chalk::Log fault: Double fault while formatting message. This means we couldn't even report the error we got while formatting.] #{e.message}\n"
|
28
|
+
end
|
29
|
+
rescue StandardError => e
|
30
|
+
# Triple fault!
|
31
|
+
"[Chalk::Log fault: Triple fault while formatting message. This means we couldn't even report the error we got while reporting the original error.]\n"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Formats a hash for logging. This is provided for (rare) use outside of log
|
36
|
+
# methods; you can pass a hash directly to log methods and this formatting
|
37
|
+
# will automatically be applied.
|
38
|
+
#
|
39
|
+
# @param hash [Hash] The hash to be formatted
|
40
|
+
def format_hash(hash)
|
41
|
+
hash.map {|k, v| display(k, v)}.join(' ')
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def do_format(event)
|
47
|
+
data = event.data
|
48
|
+
time = event.time
|
49
|
+
level = event.level
|
50
|
+
|
51
|
+
# Data provided by blocks may not be arrays yet
|
52
|
+
data = [data] unless data.kind_of?(Array)
|
53
|
+
info = data.pop if data.last.kind_of?(Hash)
|
54
|
+
error = data.pop if data.last.kind_of?(Exception)
|
55
|
+
message = data.pop if data.last.kind_of?(String)
|
56
|
+
|
57
|
+
if data.length > 0
|
58
|
+
raise Chalk::Log::InvalidArguments.new("Invalid leftover arguments: #{data.inspect}")
|
59
|
+
end
|
60
|
+
|
61
|
+
id = action_id
|
62
|
+
pid = Process.pid
|
63
|
+
|
64
|
+
pretty_print(
|
65
|
+
time: timestamp_prefix(time),
|
66
|
+
level: Chalk::Log::LEVELS[level],
|
67
|
+
action_id: id,
|
68
|
+
message: message,
|
69
|
+
error: error,
|
70
|
+
info: (info && info.merge(contextual_info || {})) || contextual_info,
|
71
|
+
pid: pid
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def pretty_print(spec)
|
76
|
+
message = build_message(spec[:message], spec[:info], spec[:error])
|
77
|
+
message = tag(message, spec[:time], spec[:pid], spec[:action_id])
|
78
|
+
message
|
79
|
+
end
|
80
|
+
|
81
|
+
def build_message(message, info, error)
|
82
|
+
# Make sure we're not mutating the message that was passed in
|
83
|
+
if message
|
84
|
+
message = message.dup
|
85
|
+
end
|
86
|
+
|
87
|
+
if message && (info || error)
|
88
|
+
message << ':'
|
89
|
+
end
|
90
|
+
|
91
|
+
if info
|
92
|
+
message << ' ' if message
|
93
|
+
message ||= ''
|
94
|
+
info!(message, info)
|
95
|
+
end
|
96
|
+
|
97
|
+
if error
|
98
|
+
message << ' ' if message
|
99
|
+
message ||= ''
|
100
|
+
error!(message, error)
|
101
|
+
end
|
102
|
+
|
103
|
+
message ||= ''
|
104
|
+
message << "\n"
|
105
|
+
message
|
106
|
+
end
|
107
|
+
|
108
|
+
# Displaying info hash
|
109
|
+
|
110
|
+
def info!(message, info)
|
111
|
+
message << format_hash(info.merge(contextual_info || {}))
|
112
|
+
end
|
113
|
+
|
114
|
+
def display(key, value)
|
115
|
+
begin
|
116
|
+
value = json(value)
|
117
|
+
rescue StandardError
|
118
|
+
value = "#{value.inspect} [JSON-FAILED]"
|
119
|
+
end
|
120
|
+
|
121
|
+
# Non-numeric simple strings don't need quotes.
|
122
|
+
if value =~ /\A"\w*[A-Za-z]\w*"\z/ &&
|
123
|
+
!['"true"', '"false"', '"null"'].include?(value)
|
124
|
+
value = value[1...-1]
|
125
|
+
end
|
126
|
+
|
127
|
+
"#{key}=#{value}"
|
128
|
+
end
|
129
|
+
|
130
|
+
# Displaying backtraces
|
131
|
+
|
132
|
+
def error!(message, error)
|
133
|
+
backtrace = error.backtrace || ['[no backtrace]']
|
134
|
+
message << display(:error_class, error.class.to_s) << " "
|
135
|
+
message << display(:error, error.to_s)
|
136
|
+
message << "\n"
|
137
|
+
message << Chalk::Log::Utils.format_backtrace(backtrace)
|
138
|
+
message << "\n"
|
139
|
+
message
|
140
|
+
end
|
141
|
+
|
142
|
+
def json(value)
|
143
|
+
# Use an Array (and trim later) because Ruby's JSON generator
|
144
|
+
# requires an array or object.
|
145
|
+
wrapped = [value]
|
146
|
+
|
147
|
+
# We may alias the raw JSON generation method. We don't care about
|
148
|
+
# emiting raw HTML tags heres, so no need to use the safe
|
149
|
+
# generation method.
|
150
|
+
if JSON.respond_to?(:unsafe_generate)
|
151
|
+
dumped = JSON.unsafe_generate(wrapped)
|
152
|
+
else
|
153
|
+
dumped = JSON.generate(wrapped)
|
154
|
+
end
|
155
|
+
|
156
|
+
dumped[1...-1] # strip off the brackets we added while array-ifying
|
157
|
+
end
|
158
|
+
|
159
|
+
def action_id
|
160
|
+
LSpace[:action_id]
|
161
|
+
end
|
162
|
+
|
163
|
+
def contextual_info
|
164
|
+
LSpace[:'chalk.log.contextual_info']
|
165
|
+
end
|
166
|
+
|
167
|
+
def tag(message, time, pid, action_id)
|
168
|
+
return message unless configatron.chalk.log.tagging
|
169
|
+
|
170
|
+
metadata = []
|
171
|
+
metadata << pid if configatron.chalk.log.pid
|
172
|
+
metadata << action_id if action_id
|
173
|
+
prefix = "[#{metadata.join('|')}] " if metadata.length > 0
|
174
|
+
|
175
|
+
if configatron.chalk.log.timestamp
|
176
|
+
prefix = "[#{time}] #{prefix}"
|
177
|
+
end
|
178
|
+
|
179
|
+
out = ''
|
180
|
+
message.split("\n").each do |line|
|
181
|
+
out << prefix << line << "\n"
|
182
|
+
end
|
183
|
+
|
184
|
+
out
|
185
|
+
end
|
186
|
+
|
187
|
+
def timestamp_prefix(now)
|
188
|
+
now_fmt = now.strftime("%Y-%m-%d %H:%M:%S")
|
189
|
+
ms_fmt = sprintf("%06d", now.usec)
|
190
|
+
"#{now_fmt}.#{ms_fmt}"
|
191
|
+
end
|
192
|
+
end
|