startback 0.17.3 → 0.18.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/lib/startback/audit/ext/context.rb +35 -0
- data/lib/startback/audit/ext.rb +1 -0
- data/lib/startback/audit/middleware.rb +30 -0
- data/lib/startback/audit/null_tracer.rb +31 -0
- data/lib/startback/audit/operation_tracer.rb +19 -0
- data/lib/startback/audit/shared.rb +18 -0
- data/lib/startback/audit/span.rb +66 -0
- data/lib/startback/audit/trace_logger.rb +30 -0
- data/lib/startback/audit/tracer.rb +85 -0
- data/lib/startback/audit.rb +7 -1
- data/lib/startback/context/h_factory.rb +21 -0
- data/lib/startback/context.rb +1 -1
- data/lib/startback/event/ext/operation.rb +4 -5
- data/lib/startback/support/fake_logger.rb +16 -5
- data/lib/startback/support/log_formatter.rb +25 -2
- data/lib/startback/support/redactor.rb +40 -0
- data/lib/startback/support.rb +1 -0
- data/lib/startback/version.rb +2 -2
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/audit/ext/test_context.rb +66 -0
- data/spec/unit/audit/test_middleware.rb +66 -0
- data/spec/unit/audit/test_trace_logger.rb +51 -0
- data/spec/unit/audit/test_tracer.rb +106 -0
- data/spec/unit/context/test_h_factory.rb +5 -3
- data/spec/unit/support/test_redactor.rb +84 -0
- metadata +20 -8
- data/lib/startback/audit/trailer.rb +0 -132
- data/spec/unit/audit/test_trailer.rb +0 -105
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ae59b533182869f37a6a720314be714a3c348d294071724c7f6f4931385d1d5
|
4
|
+
data.tar.gz: de7630071838aacc2c2a916b8aeade7e39b9e580617b8dd89f56995dba995d68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2804d9ed39c41c8ad5d3a45b7b6e53d021fe3f20fbe30fa7ed5f08e8450ee23b69870879a044b5f971cd07fff821177233bbce71ae4edb7c4acc81bc9a7910c
|
7
|
+
data.tar.gz: 9e42c4b69121a35d01aafbd42c08d0fbde53b5cbca308036b24bd6a80a840f455925f33abae6d27abfd5df9f7f69222db961db0cb1e724a417320f1c83481ffb
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Startback
|
2
|
+
class Context
|
3
|
+
attr_accessor :tracer
|
4
|
+
|
5
|
+
def tracer
|
6
|
+
@tracer ||= Audit::Tracer.empty.on_span(
|
7
|
+
Audit::TraceLogger.new(logger)
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def trace_span(attributes = {}, &block)
|
12
|
+
@tracer = tracer.new_trace unless tracer.attached?
|
13
|
+
tracer.fork(attributes, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
h_dump do |h|
|
17
|
+
next unless tracer.attached?
|
18
|
+
|
19
|
+
last_span = tracer.last_span!
|
20
|
+
h.merge!("tracing" => {
|
21
|
+
"trace_id" => last_span.trace_id,
|
22
|
+
"span_id" => last_span.span_id,
|
23
|
+
"parent_id" => last_span.parent_id,
|
24
|
+
})
|
25
|
+
end
|
26
|
+
|
27
|
+
h_factory do |c, h|
|
28
|
+
next unless h['tracing']
|
29
|
+
|
30
|
+
trace_id = h['tracing']['trace_id']
|
31
|
+
span_id = h['tracing']['span_id']
|
32
|
+
c.tracer = c.tracer.attach_to(trace_id, span_id)
|
33
|
+
end
|
34
|
+
end # class Context
|
35
|
+
end # module Startback
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'ext/context'
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Startback
|
2
|
+
module Audit
|
3
|
+
class Middleware
|
4
|
+
|
5
|
+
def initialize(app)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
context = ::Startback::Context::Middleware.context(env)
|
11
|
+
|
12
|
+
# attach to the existing trace if any
|
13
|
+
trace_id = env['HTTP_X_TRACE_ID']
|
14
|
+
span_id = env['HTTP_X_SPAN_ID']
|
15
|
+
context.tracer = context.tracer.attach_to(trace_id, span_id) if trace_id && span_id
|
16
|
+
|
17
|
+
# trace it!
|
18
|
+
context.trace_span({
|
19
|
+
:type => :request_handler,
|
20
|
+
:method => env['REQUEST_METHOD'],
|
21
|
+
:path => env['PATH_INFO'],
|
22
|
+
:qs => env['QUERY_STRING']
|
23
|
+
}) do
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end # class Middleware
|
29
|
+
end # module Audit
|
30
|
+
end # module Startback
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Startback
|
2
|
+
module Audit
|
3
|
+
class NullTracer
|
4
|
+
|
5
|
+
def attached?
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
def last_span!
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_trace(*args)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def attach_to(*args)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def fork(*args)
|
22
|
+
yield
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_span(listener = nil, &block)
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
end # class NullTracer
|
30
|
+
end # module Audit
|
31
|
+
end # module Startback
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'startback/audit'
|
2
|
+
|
3
|
+
module Startback
|
4
|
+
module Audit
|
5
|
+
class OperationTracer
|
6
|
+
include Startback::Audit::Shared
|
7
|
+
|
8
|
+
def call(runner, op, &block)
|
9
|
+
op.context.trace_span({
|
10
|
+
type: :operation,
|
11
|
+
op: op_name(op),
|
12
|
+
data: op_data(op),
|
13
|
+
context: op_context(op)
|
14
|
+
}, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
end # class OperationTracer
|
18
|
+
end # module Audit
|
19
|
+
end # module Startback
|
@@ -12,6 +12,24 @@ module Startback
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
def op_context(op)
|
16
|
+
op.respond_to?(:context, false) ? op.context.to_h : {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def op_data(op)
|
20
|
+
if op.respond_to?(:op_data, false)
|
21
|
+
op.op_data
|
22
|
+
elsif op.respond_to?(:to_trail, false)
|
23
|
+
op.to_trail
|
24
|
+
elsif op.respond_to?(:input, false)
|
25
|
+
op.input
|
26
|
+
elsif op.respond_to?(:request, false)
|
27
|
+
op.request
|
28
|
+
elsif op.is_a?(Operation::MultiOperation)
|
29
|
+
op.ops.map{ |sub_op| op_to_trail(sub_op) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
15
33
|
end # module Shared
|
16
34
|
end # module Audit
|
17
35
|
end # module Startback
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Startback
|
2
|
+
module Audit
|
3
|
+
class Span
|
4
|
+
|
5
|
+
def initialize(trace_id, parent_id, attributes = {}, span_id = SecureRandom.uuid)
|
6
|
+
@trace_id, @parent_id = trace_id, parent_id
|
7
|
+
@span_id = span_id
|
8
|
+
@attributes = attributes
|
9
|
+
@status = 'unknown'
|
10
|
+
@at = (Time.now.to_f*1000).to_i
|
11
|
+
@timing = nil
|
12
|
+
@error = nil
|
13
|
+
end
|
14
|
+
attr_reader :trace_id, :parent_id, :span_id, :status, :attributes, :timing, :error
|
15
|
+
|
16
|
+
def finished?
|
17
|
+
@status != 'unknown'
|
18
|
+
end
|
19
|
+
|
20
|
+
def success?
|
21
|
+
@status == 'success'
|
22
|
+
end
|
23
|
+
|
24
|
+
def error?
|
25
|
+
@status == 'error'
|
26
|
+
end
|
27
|
+
|
28
|
+
def fork(attributes = {})
|
29
|
+
Span.new(@trace_id, @span_id, attributes)
|
30
|
+
end
|
31
|
+
|
32
|
+
def finish(timing, error = nil)
|
33
|
+
@timing = timing
|
34
|
+
@status = error ? 'error' : 'success'
|
35
|
+
@error = error
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_h
|
40
|
+
{
|
41
|
+
:spanId => span_id,
|
42
|
+
:traceId => trace_id,
|
43
|
+
:parentId => parent_id,
|
44
|
+
:status => status,
|
45
|
+
:timing => timing_to_h,
|
46
|
+
:attributes => attributes,
|
47
|
+
:error => error,
|
48
|
+
}.compact
|
49
|
+
end
|
50
|
+
|
51
|
+
def timing_to_h
|
52
|
+
{
|
53
|
+
at: @at,
|
54
|
+
total: @timing&.total,
|
55
|
+
real: @timing&.real,
|
56
|
+
}.compact
|
57
|
+
end
|
58
|
+
private :timing_to_h
|
59
|
+
|
60
|
+
def to_json(*args, &block)
|
61
|
+
to_h.to_json(*args, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
end # class Span
|
65
|
+
end # module Audit
|
66
|
+
end # module Startback
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Startback
|
2
|
+
module Audit
|
3
|
+
class TraceLogger
|
4
|
+
|
5
|
+
def initialize(logger = default_logger)
|
6
|
+
@logger = logger || default_logger
|
7
|
+
@logger.formatter ||= Support::LogFormatter.new if @logger.respond_to?(:formatter=)
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(span)
|
11
|
+
if !span.finished?
|
12
|
+
@logger.debug(span.to_h)
|
13
|
+
elsif span.success?
|
14
|
+
@logger.info(span.to_h)
|
15
|
+
elsif span&.error.is_a?(Startback::Errors::BadRequestError)
|
16
|
+
@logger.warn(span.to_h)
|
17
|
+
else
|
18
|
+
@logger.error(span.to_h)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def default_logger
|
25
|
+
::Logger.new(STDOUT, 'daily')
|
26
|
+
end
|
27
|
+
|
28
|
+
end # class TraceLogger
|
29
|
+
end # module Audit
|
30
|
+
end # module Startback
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
module Startback
|
5
|
+
module Audit
|
6
|
+
class Tracer
|
7
|
+
|
8
|
+
def initialize(stack = [], listeners = [], redactor = default_redactor)
|
9
|
+
@stack = stack
|
10
|
+
@listeners = listeners
|
11
|
+
@redactor = redactor
|
12
|
+
end
|
13
|
+
attr_reader :stack
|
14
|
+
|
15
|
+
def self.empty
|
16
|
+
Tracer.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def attached?
|
20
|
+
!@stack.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
def last_span!
|
24
|
+
error!("Trace not attached") unless attached?
|
25
|
+
|
26
|
+
@stack.last
|
27
|
+
end
|
28
|
+
|
29
|
+
def new_trace(attributes = {})
|
30
|
+
error!("Trace already attached") if attached?
|
31
|
+
|
32
|
+
attach_to(SecureRandom.uuid, SecureRandom.uuid, attributes)
|
33
|
+
end
|
34
|
+
|
35
|
+
def attach_to(trace_id, span_id, attributes = {}, parent_id = nil)
|
36
|
+
error!("Trace already attached") if attached?
|
37
|
+
|
38
|
+
initial_span = Span.new(trace_id, parent_id, attributes, span_id)
|
39
|
+
initial_stack = [ initial_span ]
|
40
|
+
Tracer.new(initial_stack, @listeners, @redactor)
|
41
|
+
end
|
42
|
+
|
43
|
+
def fork(attributes = {}, &block)
|
44
|
+
span = last_span!.fork(@redactor.redact(attributes))
|
45
|
+
@stack << span
|
46
|
+
propagate_to_listeners(span)
|
47
|
+
result, error = nil, nil
|
48
|
+
timing = Benchmark.measure do
|
49
|
+
result, error = exec_block_with_error_handling(block)
|
50
|
+
end
|
51
|
+
span = @stack.pop.finish(timing, error)
|
52
|
+
propagate_to_listeners(span)
|
53
|
+
error ? raise(error) : result
|
54
|
+
end
|
55
|
+
|
56
|
+
def on_span(listener = nil, &block)
|
57
|
+
@listeners << (listener || block)
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def default_redactor
|
64
|
+
Support::Redactor.new
|
65
|
+
end
|
66
|
+
|
67
|
+
def exec_block_with_error_handling(block)
|
68
|
+
[ block.call, nil ]
|
69
|
+
rescue => ex
|
70
|
+
[ nil, ex ]
|
71
|
+
end
|
72
|
+
|
73
|
+
def error!(msg)
|
74
|
+
raise Startback::Errors::InternalServerError, msg
|
75
|
+
end
|
76
|
+
|
77
|
+
def propagate_to_listeners(span)
|
78
|
+
@listeners.each do |listener|
|
79
|
+
listener.call(span)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end # class Tracer
|
84
|
+
end # module Audit
|
85
|
+
end # module Startback
|
data/lib/startback/audit.rb
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
require_relative 'audit/ext'
|
1
2
|
require_relative 'audit/shared'
|
2
|
-
require_relative 'audit/trailer'
|
3
3
|
require_relative 'audit/prometheus'
|
4
|
+
require_relative 'audit/span'
|
5
|
+
require_relative 'audit/null_tracer'
|
6
|
+
require_relative 'audit/tracer'
|
7
|
+
require_relative 'audit/operation_tracer'
|
8
|
+
require_relative 'audit/trace_logger'
|
9
|
+
require_relative 'audit/middleware'
|
@@ -2,6 +2,11 @@ module Startback
|
|
2
2
|
class Context
|
3
3
|
module HFactory
|
4
4
|
|
5
|
+
def inherited(subclass)
|
6
|
+
subclass.h_factories = h_factories.dup if h_factories?
|
7
|
+
subclass.h_dumpers = h_dumpers.dup if h_dumpers?
|
8
|
+
end
|
9
|
+
|
5
10
|
def h(hash)
|
6
11
|
h_factor!(self.new, hash)
|
7
12
|
end
|
@@ -13,6 +18,14 @@ module Startback
|
|
13
18
|
context
|
14
19
|
end
|
15
20
|
|
21
|
+
def h_factories?
|
22
|
+
!!@h_factories && @h_factories.any?
|
23
|
+
end
|
24
|
+
|
25
|
+
def h_factories=(factories)
|
26
|
+
@h_factories = factories
|
27
|
+
end
|
28
|
+
|
16
29
|
def h_factories
|
17
30
|
@h_factories ||= []
|
18
31
|
end
|
@@ -30,10 +43,18 @@ module Startback
|
|
30
43
|
hash
|
31
44
|
end
|
32
45
|
|
46
|
+
def h_dumpers?
|
47
|
+
!!@h_dumpers && @h_dumpers.any?
|
48
|
+
end
|
49
|
+
|
33
50
|
def h_dumpers
|
34
51
|
@h_dumpers ||= []
|
35
52
|
end
|
36
53
|
|
54
|
+
def h_dumpers=(dumpers)
|
55
|
+
@h_dumpers = dumpers
|
56
|
+
end
|
57
|
+
|
37
58
|
def h_dump(&dumper)
|
38
59
|
h_dumpers << dumper
|
39
60
|
end
|
data/lib/startback/context.rb
CHANGED
@@ -3,11 +3,10 @@ module Startback
|
|
3
3
|
|
4
4
|
def self.emits(type, &bl)
|
5
5
|
after_call do
|
6
|
-
event_data = instance_exec(&bl)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
context.engine.bus.emit(event)
|
6
|
+
if event_data = instance_exec(&bl)
|
7
|
+
event = type.new(type.to_s, event_data, context)
|
8
|
+
context.engine.bus.emit(event)
|
9
|
+
end
|
11
10
|
end
|
12
11
|
end
|
13
12
|
|
@@ -3,16 +3,27 @@ module Startback
|
|
3
3
|
class FakeLogger < Logger
|
4
4
|
|
5
5
|
def initialize(*args)
|
6
|
-
@
|
6
|
+
@seen = []
|
7
|
+
end
|
8
|
+
attr_accessor :formatter
|
9
|
+
attr_reader :seen
|
10
|
+
|
11
|
+
def last_msg
|
12
|
+
seen.last
|
7
13
|
end
|
8
|
-
attr_reader :last_msg
|
9
14
|
|
10
15
|
[:debug, :info, :warn, :error, :fatal].each do |meth|
|
11
16
|
define_method(meth) do |msg|
|
12
|
-
@
|
13
|
-
end
|
17
|
+
@seen << format(meth, msg)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def format(severity, message)
|
22
|
+
return message unless formatter
|
23
|
+
|
24
|
+
formatter.call(severity.to_s.upcase, Time.now, 'prognam', message)
|
14
25
|
end
|
15
26
|
|
16
|
-
end # class
|
27
|
+
end # class FakeLogger
|
17
28
|
end # module Support
|
18
29
|
end # module Startback
|
@@ -2,16 +2,33 @@ module Startback
|
|
2
2
|
module Support
|
3
3
|
class LogFormatter
|
4
4
|
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
pretty_print: nil
|
7
|
+
}
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
11
|
+
@options[:pretty_print] ||= auto_pretty_print
|
12
|
+
end
|
13
|
+
|
14
|
+
def pretty_print?
|
15
|
+
!!@options[:pretty_print]
|
16
|
+
end
|
17
|
+
|
5
18
|
def call(severity, time, progname, msg)
|
6
19
|
msg = { message: msg } if msg.is_a?(String)
|
7
20
|
msg = { error: msg } if msg.is_a?(Exception)
|
8
|
-
{
|
21
|
+
data = {
|
9
22
|
severity: severity,
|
10
23
|
time: time
|
11
24
|
}.merge(msg)
|
12
25
|
.merge(error: error_to_json(msg[:error], severity))
|
13
26
|
.compact
|
14
|
-
|
27
|
+
if pretty_print?
|
28
|
+
JSON.pretty_generate(data) << "\n"
|
29
|
+
else
|
30
|
+
data.to_json << "\n"
|
31
|
+
end
|
15
32
|
end
|
16
33
|
|
17
34
|
def error_to_json(error, severity = nil)
|
@@ -29,6 +46,12 @@ module Startback
|
|
29
46
|
}.compact
|
30
47
|
end
|
31
48
|
|
49
|
+
private
|
50
|
+
|
51
|
+
def auto_pretty_print
|
52
|
+
ENV['RACK_ENV'] != 'production'
|
53
|
+
end
|
54
|
+
|
32
55
|
end # class LogFormatter
|
33
56
|
end # module Support
|
34
57
|
end # module Startback
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Startback
|
2
|
+
module Support
|
3
|
+
class Redactor
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
|
7
|
+
# Words used to stop dumping for, e.g., security reasons
|
8
|
+
blacklist: "token password secret credential email address"
|
9
|
+
|
10
|
+
}
|
11
|
+
|
12
|
+
def initialize(options = {})
|
13
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def redact(data)
|
17
|
+
case data
|
18
|
+
when Hash, OpenStruct
|
19
|
+
Hash[data.map{|(k,v)|
|
20
|
+
[k, (k.to_s =~ blacklist_rx) ? '---redacted---' : redact(v)]
|
21
|
+
}]
|
22
|
+
when Enumerable
|
23
|
+
data.map{|elm| redact(elm) }.compact
|
24
|
+
else
|
25
|
+
data
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def blacklist_rx
|
32
|
+
@blacklist_rx ||= Regexp.new(
|
33
|
+
@options[:blacklist].split(/\s+/).join("|"),
|
34
|
+
Regexp::IGNORECASE
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
end # class Redactor
|
39
|
+
end # module Support
|
40
|
+
end # module Startback
|
data/lib/startback/support.rb
CHANGED
data/lib/startback/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Startback
|
4
|
+
module Audit
|
5
|
+
describe "Context extension" do
|
6
|
+
subject do
|
7
|
+
context.to_h
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'no tracing has been added' do
|
11
|
+
let(:context) do
|
12
|
+
Startback::Context.new
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'has no tracing info' do
|
16
|
+
expect(subject).not_to have_key('tracing')
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'does not break reloading a context if no tracing' do
|
20
|
+
subject = Startback::Context.h({})
|
21
|
+
expect(subject.tracer).to be_an_instance_of(Startback::Audit::Tracer)
|
22
|
+
expect(subject.tracer).not_to be_attached
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'some tracing has been added and attached' do
|
27
|
+
let(:context) do
|
28
|
+
Startback::Context.new.dup do |c|
|
29
|
+
c.tracer = Tracer.empty.attach_to('some_trace_uuid', 'some_trace_parent_uuid')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has tracing info' do
|
34
|
+
expect(subject).to have_key('tracing')
|
35
|
+
expect(subject['tracing']['trace_id']).to eql('some_trace_uuid')
|
36
|
+
expect(subject['tracing']['span_id']).to eql('some_trace_parent_uuid')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'helps reloading a tracer instance from h info' do
|
40
|
+
subject = Startback::Context.h('tracing' => {
|
41
|
+
'trace_id' => 'some_trace_uuid',
|
42
|
+
'parent_id' => 'some_trace_parent_uuid'
|
43
|
+
})
|
44
|
+
expect(subject.tracer).to be_an_instance_of(Startback::Audit::Tracer)
|
45
|
+
expect(subject.tracer).to be_attached
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'some tracing has been added and attached on a subclass' do
|
50
|
+
let(:context) do
|
51
|
+
SubContext.new.dup do |c|
|
52
|
+
c.tracer = Tracer.empty.attach_to('some_trace_uuid', 'some_trace_parent_uuid')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'has tracing info' do
|
57
|
+
expect(context.tracer).to be_attached
|
58
|
+
expect(SubContext.h_factories.size).to eql(3)
|
59
|
+
expect(subject).to have_key('tracing')
|
60
|
+
expect(subject['tracing']['trace_id']).to eql('some_trace_uuid')
|
61
|
+
expect(subject['tracing']['span_id']).to eql('some_trace_parent_uuid')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end # module Audit
|
66
|
+
end # module Startback
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Startback
|
4
|
+
module Audit
|
5
|
+
describe Middleware do
|
6
|
+
include Rack::Test::Methods
|
7
|
+
|
8
|
+
def app
|
9
|
+
Rack::Builder.new do
|
10
|
+
use Startback::Context::Middleware
|
11
|
+
use Middleware
|
12
|
+
run ->(env) {
|
13
|
+
ctx = env[Startback::Context::Middleware::RACK_ENV_KEY]
|
14
|
+
attached = ctx.tracer.attached?
|
15
|
+
last_span = ctx.tracer.last_span!
|
16
|
+
[200, {
|
17
|
+
'tracer-attached' => attached,
|
18
|
+
'last-span' => last_span
|
19
|
+
}, 'ok']
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when not provided with tracing headers' do
|
25
|
+
it 'starts a new trace' do
|
26
|
+
get '/'
|
27
|
+
expect(last_response.status).to eql(200)
|
28
|
+
expect(last_response.headers['tracer-attached']).to eq(true)
|
29
|
+
expect(last_response.body).to eql("ok")
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'creates a fresh new span' do
|
33
|
+
get '/'
|
34
|
+
expect(last_response.status).to eql(200)
|
35
|
+
expect(last_response.headers['last-span']).not_to be_nil
|
36
|
+
expect(last_response.body).to eql("ok")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'when provided with tracing headers' do
|
41
|
+
it 'does attach the tracer' do
|
42
|
+
header('X-Trace-Id', 'trace-id')
|
43
|
+
header('X-Span-Id', 'span-id')
|
44
|
+
get '/'
|
45
|
+
expect(last_response.status).to eql(200)
|
46
|
+
expect(last_response.headers['tracer-attached']).to eq(true)
|
47
|
+
expect(last_response.body).to eql("ok")
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'does create a new span' do
|
51
|
+
header('X-Trace-Id', 'trace-id')
|
52
|
+
header('X-Span-Id', 'span-id')
|
53
|
+
get '/'
|
54
|
+
expect(last_response.status).to eql(200)
|
55
|
+
|
56
|
+
span = last_response.headers['last-span']
|
57
|
+
expect(span).not_to be_nil
|
58
|
+
expect(span.trace_id).to eq('trace-id')
|
59
|
+
expect(span.parent_id).to eq('span-id')
|
60
|
+
expect(span.span_id).not_to be_nil
|
61
|
+
expect(span.span_id).not_to eq('span-id')
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end # describe Middleware
|
65
|
+
end # module Audit
|
66
|
+
end # module Startback
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Startback
|
4
|
+
module Audit
|
5
|
+
describe TraceLogger do
|
6
|
+
|
7
|
+
let(:fake_logger) do
|
8
|
+
Support::FakeLogger.new
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:trace_logger) do
|
12
|
+
TraceLogger.new(fake_logger)
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:tracer) do
|
16
|
+
Tracer.new.on_span(trace_logger)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'helps logging successes' do
|
20
|
+
attached = tracer.attach_to('trace', 'root-span')
|
21
|
+
attached.fork(foo: 'bar') do
|
22
|
+
"hello world"
|
23
|
+
end
|
24
|
+
expect(fake_logger.seen.size).to eql(2)
|
25
|
+
expect(fake_logger.seen.first).to match(/DEBUG/)
|
26
|
+
expect(fake_logger.seen.last).to match(/INFO/)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'helps logging user errors' do
|
30
|
+
attached = tracer.attach_to('trace', 'root-span')
|
31
|
+
attached.fork(foo: 'bar') do
|
32
|
+
raise Startback::Errors::ForbiddenError, "no such access granted"
|
33
|
+
end rescue nil
|
34
|
+
expect(fake_logger.seen.size).to eql(2)
|
35
|
+
expect(fake_logger.seen.first).to match(/DEBUG/)
|
36
|
+
expect(fake_logger.seen.last).to match(/WARN/)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'helps logging fatal errors' do
|
40
|
+
attached = tracer.attach_to('trace', 'root-span')
|
41
|
+
attached.fork(foo: 'bar') do
|
42
|
+
raise ArgumentError, "something bad"
|
43
|
+
end rescue nil
|
44
|
+
expect(fake_logger.seen.size).to eql(2)
|
45
|
+
expect(fake_logger.seen.first).to match(/DEBUG/)
|
46
|
+
expect(fake_logger.seen.last).to match(/ERROR/)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Startback
|
4
|
+
module Audit
|
5
|
+
describe Tracer do
|
6
|
+
|
7
|
+
let(:spans_seen) do
|
8
|
+
[]
|
9
|
+
end
|
10
|
+
|
11
|
+
subject do
|
12
|
+
Tracer.new.on_span do |span|
|
13
|
+
spans_seen << span
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'clones the object on attach_to' do
|
18
|
+
expect(subject).not_to be_attached
|
19
|
+
attached = subject.attach_to('the-trace', 'root-span')
|
20
|
+
expect(attached).to be_a(Tracer)
|
21
|
+
expect(attached).not_to be(subject)
|
22
|
+
expect(attached).to be_attached
|
23
|
+
expect(subject).not_to be_attached
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'fork' do
|
27
|
+
it 'fails if not attached' do
|
28
|
+
expect(subject).not_to be_attached
|
29
|
+
expect {
|
30
|
+
subject.fork do
|
31
|
+
12
|
32
|
+
end
|
33
|
+
}.to raise_error(/attached/)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns the block result' do
|
37
|
+
attached = subject.attach_to('the-trace', 'root-span')
|
38
|
+
result = attached.fork do
|
39
|
+
'foo'
|
40
|
+
end
|
41
|
+
expect(result).to eql('foo')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'propagates spans to listeners' do
|
45
|
+
attached = subject.attach_to('the-trace', 'root-span')
|
46
|
+
result = attached.fork do
|
47
|
+
'foo'
|
48
|
+
end
|
49
|
+
expect(spans_seen.size).to eql(2)
|
50
|
+
expect(spans_seen.last).to be_a(Span)
|
51
|
+
expect(spans_seen.last.timing).not_to be_nil
|
52
|
+
expect(spans_seen.last).to be_finished
|
53
|
+
expect(spans_seen.last).to be_success
|
54
|
+
expect(spans_seen.last).not_to be_error
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'reraises any error that occurs' do
|
58
|
+
attached = subject.attach_to('the-trace', 'root-span')
|
59
|
+
expect {
|
60
|
+
attached.fork do
|
61
|
+
raise ArgumentError, "An error"
|
62
|
+
end
|
63
|
+
}.to raise_error(ArgumentError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'traces any error that occurs' do
|
67
|
+
attached = subject.attach_to('the-trace', 'root-span')
|
68
|
+
expect {
|
69
|
+
attached.fork do
|
70
|
+
raise ArgumentError, "An error"
|
71
|
+
end
|
72
|
+
}.to raise_error(ArgumentError)
|
73
|
+
expect(spans_seen.size).to eql(2)
|
74
|
+
expect(spans_seen.last).to be_a(Span)
|
75
|
+
expect(spans_seen.last.timing).not_to be_nil
|
76
|
+
expect(spans_seen.last).to be_finished
|
77
|
+
expect(spans_seen.last).not_to be_success
|
78
|
+
expect(spans_seen.last).to be_error
|
79
|
+
expect(spans_seen.last.error).to be_a(ArgumentError)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'redacting' do
|
84
|
+
it 'applies by default' do
|
85
|
+
attached = subject.attach_to('the-trace', 'root-span')
|
86
|
+
result = attached.fork({
|
87
|
+
foo: 'bar',
|
88
|
+
password: 'baz',
|
89
|
+
repeatPassword: 'baz'
|
90
|
+
}) do
|
91
|
+
'foo'
|
92
|
+
end
|
93
|
+
expect(spans_seen.size).to eql(2)
|
94
|
+
expect(spans_seen.last).to be_a(Span)
|
95
|
+
expect(spans_seen.last.attributes).to eql({
|
96
|
+
foo: 'bar',
|
97
|
+
password: '---redacted---',
|
98
|
+
repeatPassword: '---redacted---'
|
99
|
+
})
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
@@ -7,9 +7,11 @@ module Startback
|
|
7
7
|
expect(Context.new.to_json).to eql("{}")
|
8
8
|
end
|
9
9
|
|
10
|
-
it 'allows installing factories' do
|
11
|
-
expect(Context.h_factories).to
|
12
|
-
expect(SubContext.h_factories.size).to eql(
|
10
|
+
it 'allows installing factories and dumpers' do
|
11
|
+
expect(Context.h_factories.size).to eql(1)
|
12
|
+
expect(SubContext.h_factories.size).to eql(3)
|
13
|
+
expect(Context.h_dumpers.size).to eql(1)
|
14
|
+
expect(SubContext.h_dumpers.size).to eql(3)
|
13
15
|
end
|
14
16
|
|
15
17
|
it 'has a `to_h` information contract that works as expected' do
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Startback
|
4
|
+
module Support
|
5
|
+
describe Redactor do
|
6
|
+
|
7
|
+
let(:redactor) do
|
8
|
+
Redactor.new
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'applies default blacklists for security reasons' do
|
12
|
+
data = {
|
13
|
+
token: "will not be dumped",
|
14
|
+
a_token: "will not be dumped",
|
15
|
+
AToken: "will not be dumped",
|
16
|
+
password: "will not be dumped",
|
17
|
+
secret: "will not be dumped",
|
18
|
+
credentials: "will not be dumped",
|
19
|
+
foo: "bar"
|
20
|
+
}
|
21
|
+
expect(redactor.redact(data)).to eql({
|
22
|
+
token: "---redacted---",
|
23
|
+
a_token: "---redacted---",
|
24
|
+
AToken: "---redacted---",
|
25
|
+
password: "---redacted---",
|
26
|
+
secret: "---redacted---",
|
27
|
+
credentials: "---redacted---",
|
28
|
+
foo: "bar",
|
29
|
+
})
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'applies default blacklists to data arrays too' do
|
33
|
+
data = [{
|
34
|
+
token: "will not be dumped",
|
35
|
+
a_token: "will not be dumped",
|
36
|
+
AToken: "will not be dumped",
|
37
|
+
password: "will not be dumped",
|
38
|
+
secret: "will not be dumped",
|
39
|
+
credentials: "will not be dumped",
|
40
|
+
foo: "bar"
|
41
|
+
}]
|
42
|
+
expect(redactor.redact(data)).to eql([{
|
43
|
+
token: "---redacted---",
|
44
|
+
a_token: "---redacted---",
|
45
|
+
AToken: "---redacted---",
|
46
|
+
password: "---redacted---",
|
47
|
+
secret: "---redacted---",
|
48
|
+
credentials: "---redacted---",
|
49
|
+
foo: "bar"
|
50
|
+
}])
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'redacts recursively' do
|
54
|
+
data = [{
|
55
|
+
foo: "bar",
|
56
|
+
baz: {
|
57
|
+
password: 'will not be dumped'
|
58
|
+
}
|
59
|
+
}]
|
60
|
+
expect(redactor.redact(data)).to eql([{
|
61
|
+
foo: "bar",
|
62
|
+
baz: {
|
63
|
+
password: '---redacted---'
|
64
|
+
}
|
65
|
+
}])
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'uses the stop words provided at construction' do
|
69
|
+
r = Redactor.new(blacklist: "hello and world")
|
70
|
+
data = {
|
71
|
+
Hello: "bar",
|
72
|
+
World: "foo",
|
73
|
+
foo: "bar"
|
74
|
+
}
|
75
|
+
expect(r.redact(data)).to eql({
|
76
|
+
Hello: "---redacted---",
|
77
|
+
World: "---redacted---",
|
78
|
+
foo: "bar"
|
79
|
+
})
|
80
|
+
end
|
81
|
+
|
82
|
+
end # class Redactor
|
83
|
+
end # module Support
|
84
|
+
end # module Startback
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: startback
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Bernard Lambeau
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-05-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -270,20 +270,20 @@ dependencies:
|
|
270
270
|
requirements:
|
271
271
|
- - ">="
|
272
272
|
- !ruby/object:Gem::Version
|
273
|
-
version: 0.
|
273
|
+
version: 0.21.0
|
274
274
|
- - "<"
|
275
275
|
- !ruby/object:Gem::Version
|
276
|
-
version: 0.
|
276
|
+
version: 0.22.0
|
277
277
|
type: :runtime
|
278
278
|
prerelease: false
|
279
279
|
version_requirements: !ruby/object:Gem::Requirement
|
280
280
|
requirements:
|
281
281
|
- - ">="
|
282
282
|
- !ruby/object:Gem::Version
|
283
|
-
version: 0.
|
283
|
+
version: 0.21.0
|
284
284
|
- - "<"
|
285
285
|
- !ruby/object:Gem::Version
|
286
|
-
version: 0.
|
286
|
+
version: 0.22.0
|
287
287
|
- !ruby/object:Gem::Dependency
|
288
288
|
name: tzinfo
|
289
289
|
requirement: !ruby/object:Gem::Requirement
|
@@ -382,9 +382,16 @@ files:
|
|
382
382
|
- Rakefile
|
383
383
|
- lib/startback.rb
|
384
384
|
- lib/startback/audit.rb
|
385
|
+
- lib/startback/audit/ext.rb
|
386
|
+
- lib/startback/audit/ext/context.rb
|
387
|
+
- lib/startback/audit/middleware.rb
|
388
|
+
- lib/startback/audit/null_tracer.rb
|
389
|
+
- lib/startback/audit/operation_tracer.rb
|
385
390
|
- lib/startback/audit/prometheus.rb
|
386
391
|
- lib/startback/audit/shared.rb
|
387
|
-
- lib/startback/audit/
|
392
|
+
- lib/startback/audit/span.rb
|
393
|
+
- lib/startback/audit/trace_logger.rb
|
394
|
+
- lib/startback/audit/tracer.rb
|
388
395
|
- lib/startback/caching/entity_cache.rb
|
389
396
|
- lib/startback/caching/no_store.rb
|
390
397
|
- lib/startback/caching/store.rb
|
@@ -419,6 +426,7 @@ files:
|
|
419
426
|
- lib/startback/support/log_formatter.rb
|
420
427
|
- lib/startback/support/logger.rb
|
421
428
|
- lib/startback/support/operation_runner.rb
|
429
|
+
- lib/startback/support/redactor.rb
|
422
430
|
- lib/startback/support/robustness.rb
|
423
431
|
- lib/startback/support/transaction_manager.rb
|
424
432
|
- lib/startback/support/transaction_policy.rb
|
@@ -436,8 +444,11 @@ files:
|
|
436
444
|
- lib/startback/web/prometheus.rb
|
437
445
|
- lib/startback/web/shield.rb
|
438
446
|
- spec/spec_helper.rb
|
447
|
+
- spec/unit/audit/ext/test_context.rb
|
448
|
+
- spec/unit/audit/test_middleware.rb
|
439
449
|
- spec/unit/audit/test_prometheus.rb
|
440
|
-
- spec/unit/audit/
|
450
|
+
- spec/unit/audit/test_trace_logger.rb
|
451
|
+
- spec/unit/audit/test_tracer.rb
|
441
452
|
- spec/unit/caching/test_entity_cache.rb
|
442
453
|
- spec/unit/context/test_abstraction_factory.rb
|
443
454
|
- spec/unit/context/test_dup.rb
|
@@ -454,6 +465,7 @@ files:
|
|
454
465
|
- spec/unit/support/operation_runner/test_before_after_call.rb
|
455
466
|
- spec/unit/support/test_data_object.rb
|
456
467
|
- spec/unit/support/test_env.rb
|
468
|
+
- spec/unit/support/test_redactor.rb
|
457
469
|
- spec/unit/support/test_robusteness.rb
|
458
470
|
- spec/unit/support/test_transaction_manager.rb
|
459
471
|
- spec/unit/support/test_world.rb
|
@@ -1,132 +0,0 @@
|
|
1
|
-
require_relative 'shared'
|
2
|
-
require 'forwardable'
|
3
|
-
module Startback
|
4
|
-
module Audit
|
5
|
-
#
|
6
|
-
# Log & Audit trail abstraction, that can be registered as an around
|
7
|
-
# hook on OperationRunner and as an actual logger on Context instances.
|
8
|
-
#
|
9
|
-
# The trail is outputted as JSON lines, using a Logger on the "device"
|
10
|
-
# passed at construction. The following JSON entries are dumped:
|
11
|
-
#
|
12
|
-
# - severity : INFO or ERROR
|
13
|
-
# - time : ISO8601 Datetime of operation execution
|
14
|
-
# - op : class name of the operation executed
|
15
|
-
# - op_took : Execution duration of the operation
|
16
|
-
# - op_data : Dump of operation input data
|
17
|
-
# - context : Execution context, through its `h` information contract (IC)
|
18
|
-
#
|
19
|
-
# Dumping of operation data follows the following duck typing conventions:
|
20
|
-
#
|
21
|
-
# - If the operation instance responds to `to_trail`, this data is taken
|
22
|
-
# - If the operation instance responds to `input`, this data is taken
|
23
|
-
# - If the operation instance responds to `request`, this data is taken
|
24
|
-
# - Otherwise op_data is a JSON null
|
25
|
-
#
|
26
|
-
# By contributing to the Context's `h` IC, users can easily dump information that
|
27
|
-
# makes sense (such as the operation execution requester).
|
28
|
-
#
|
29
|
-
# The class implements a sanitization process when dumping the context and
|
30
|
-
# operation data. Blacklisted words taken in construction options are used to
|
31
|
-
# prevent dumping hash keys that match them (insentively). Default stop words
|
32
|
-
# are equivalent to:
|
33
|
-
#
|
34
|
-
# Trailer.new("/var/log/trail.log", {
|
35
|
-
# blacklist: "token password secret credential"
|
36
|
-
# })
|
37
|
-
#
|
38
|
-
# Please note that the sanitization process does not apply recursively if
|
39
|
-
# the operation data is hierarchic. It only applies to the top object of
|
40
|
-
# Hash and [Hash]. Use `Operation#to_trail` to fine-tune your audit trail.
|
41
|
-
#
|
42
|
-
# Given that this Trailer is intended to be used as around hook on an
|
43
|
-
# `OperationRunner`, operations that fail at construction time will not be
|
44
|
-
# trailed at all, since they can't be ran in the first place. This may lead
|
45
|
-
# to trails not containing important errors cases if operations check their
|
46
|
-
# input at construction time.
|
47
|
-
#
|
48
|
-
class Trailer
|
49
|
-
include Shared
|
50
|
-
extend Forwardable
|
51
|
-
def_delegators :@logger, :debug, :info, :warn, :error, :fatal
|
52
|
-
|
53
|
-
DEFAULT_OPTIONS = {
|
54
|
-
|
55
|
-
# Words used to stop dumping for, e.g., security reasons
|
56
|
-
blacklist: "token password secret credential"
|
57
|
-
|
58
|
-
}
|
59
|
-
|
60
|
-
def initialize(device, options = {})
|
61
|
-
@options = DEFAULT_OPTIONS.merge(options)
|
62
|
-
@logger = ::Logger.new(device, 'daily')
|
63
|
-
@logger.formatter = Support::LogFormatter.new
|
64
|
-
end
|
65
|
-
attr_reader :logger, :options
|
66
|
-
|
67
|
-
def call(runner, op)
|
68
|
-
result = nil
|
69
|
-
time = Benchmark.realtime{ result = yield }
|
70
|
-
logger.info(op_to_trail(op, time))
|
71
|
-
result
|
72
|
-
rescue Startback::Errors::BadRequestError => ex
|
73
|
-
logger.warn(op_to_trail(op, time, ex))
|
74
|
-
raise
|
75
|
-
rescue => ex
|
76
|
-
logger.error(op_to_trail(op, time, ex))
|
77
|
-
raise
|
78
|
-
end
|
79
|
-
|
80
|
-
protected
|
81
|
-
|
82
|
-
def op_to_trail(op, time = nil, ex = nil)
|
83
|
-
log_msg = {
|
84
|
-
op_took: time ? time.round(8) : nil,
|
85
|
-
op: op_name(op),
|
86
|
-
context: op_context(op),
|
87
|
-
op_data: op_data(op)
|
88
|
-
}.compact
|
89
|
-
log_msg[:error] = ex if ex
|
90
|
-
log_msg
|
91
|
-
end
|
92
|
-
|
93
|
-
def op_context(op)
|
94
|
-
sanitize(op.respond_to?(:context, false) ? op.context.to_h : {})
|
95
|
-
end
|
96
|
-
|
97
|
-
def op_data(op)
|
98
|
-
data = if op.respond_to?(:op_data, false)
|
99
|
-
op.op_data
|
100
|
-
elsif op.respond_to?(:to_trail, false)
|
101
|
-
op.to_trail
|
102
|
-
elsif op.respond_to?(:input, false)
|
103
|
-
op.input
|
104
|
-
elsif op.respond_to?(:request, false)
|
105
|
-
op.request
|
106
|
-
elsif op.is_a?(Operation::MultiOperation)
|
107
|
-
op.ops.map{ |sub_op| op_to_trail(sub_op) }
|
108
|
-
end
|
109
|
-
sanitize(data)
|
110
|
-
end
|
111
|
-
|
112
|
-
def sanitize(data)
|
113
|
-
case data
|
114
|
-
when Hash, OpenStruct
|
115
|
-
data.dup.delete_if{|k| k.to_s =~ blacklist_rx }
|
116
|
-
when Enumerable
|
117
|
-
data.map{|elm| sanitize(elm) }.compact
|
118
|
-
else
|
119
|
-
data
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
def blacklist_rx
|
124
|
-
@blacklist_rx ||= Regexp.new(
|
125
|
-
options[:blacklist].split(/\s+/).join("|"),
|
126
|
-
Regexp::IGNORECASE
|
127
|
-
)
|
128
|
-
end
|
129
|
-
|
130
|
-
end # class Trailer
|
131
|
-
end # module Audit
|
132
|
-
end # module Startback
|
@@ -1,105 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'startback/audit'
|
3
|
-
module Startback
|
4
|
-
module Audit
|
5
|
-
describe Trailer do
|
6
|
-
|
7
|
-
let(:trailer) {
|
8
|
-
Trailer.new("/tmp/trail.log")
|
9
|
-
}
|
10
|
-
|
11
|
-
describe "op_name" do
|
12
|
-
|
13
|
-
def op_name(op, trailer = self.trailer)
|
14
|
-
trailer.send(:op_name, op)
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'uses op_name in priority if provided' do
|
18
|
-
op = OpenStruct.new(op_name: "foo")
|
19
|
-
expect(op_name(op)).to eql("foo")
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
describe "op_data" do
|
24
|
-
|
25
|
-
def op_data(op, trailer = self.trailer)
|
26
|
-
trailer.send(:op_data, op)
|
27
|
-
end
|
28
|
-
|
29
|
-
it 'uses op_data in priority if provided' do
|
30
|
-
op = OpenStruct.new(op_data: { foo: "bar" }, input: 12, request: 13)
|
31
|
-
expect(op_data(op)).to eql({ foo: "bar" })
|
32
|
-
end
|
33
|
-
|
34
|
-
it 'uses to_trail then' do
|
35
|
-
op = OpenStruct.new(to_trail: { foo: "bar" }, input: 12, request: 13)
|
36
|
-
expect(op_data(op)).to eql({ foo: "bar" })
|
37
|
-
end
|
38
|
-
|
39
|
-
it 'uses input then' do
|
40
|
-
op = OpenStruct.new(input: { foo: "bar" }, request: 13)
|
41
|
-
expect(op_data(op)).to eql({ foo: "bar" })
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'uses request then' do
|
45
|
-
op = OpenStruct.new(request: { foo: "bar" })
|
46
|
-
expect(op_data(op)).to eql({ foo: "bar" })
|
47
|
-
end
|
48
|
-
|
49
|
-
it 'applies default blacklists for security reasons' do
|
50
|
-
op = OpenStruct.new(input: {
|
51
|
-
token: "will not be dumped",
|
52
|
-
a_token: "will not be dumped",
|
53
|
-
AToken: "will not be dumped",
|
54
|
-
password: "will not be dumped",
|
55
|
-
secret: "will not be dumped",
|
56
|
-
credentials: "will not be dumped",
|
57
|
-
foo: "bar"
|
58
|
-
})
|
59
|
-
expect(op_data(op)).to eql({
|
60
|
-
foo: "bar"
|
61
|
-
})
|
62
|
-
end
|
63
|
-
|
64
|
-
it 'applies default blacklists to data arrays too' do
|
65
|
-
op = OpenStruct.new(input: [{
|
66
|
-
token: "will not be dumped",
|
67
|
-
a_token: "will not be dumped",
|
68
|
-
AToken: "will not be dumped",
|
69
|
-
password: "will not be dumped",
|
70
|
-
secret: "will not be dumped",
|
71
|
-
credentials: "will not be dumped",
|
72
|
-
foo: "bar"
|
73
|
-
}])
|
74
|
-
expect(op_data(op)).to eql([{
|
75
|
-
foo: "bar"
|
76
|
-
}])
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'uses the stop words provided at construction' do
|
80
|
-
t = Trailer.new("/tmp/trail.log", blacklist: "hello and world")
|
81
|
-
op = OpenStruct.new(request: { Hello: "bar", World: "foo", foo: "bar" })
|
82
|
-
expect(op_data(op, t)).to eql({ foo: "bar" })
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "op_context" do
|
88
|
-
|
89
|
-
def op_context(op, trailer = self.trailer)
|
90
|
-
trailer.send(:op_context, op)
|
91
|
-
end
|
92
|
-
|
93
|
-
it 'applies default blacklists for security reasons' do
|
94
|
-
op = OpenStruct.new(context: {
|
95
|
-
token: "will not be dumped",
|
96
|
-
foo: "bar"
|
97
|
-
})
|
98
|
-
expect(op_context(op)).to eql({ foo: "bar" })
|
99
|
-
end
|
100
|
-
|
101
|
-
end
|
102
|
-
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|