chalk-log 0.1.2
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.
- 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
|