exel 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +5 -4
- data/.rubocop_todo.yml +1 -13
- data/Gemfile +1 -0
- data/Guardfile +1 -0
- data/README.md +95 -30
- data/Rakefile +1 -0
- data/exel.gemspec +2 -2
- data/lib/exel.rb +6 -1
- data/lib/exel/ast_node.rb +4 -9
- data/lib/exel/context.rb +1 -0
- data/lib/exel/deferred_context_value.rb +1 -0
- data/lib/exel/error/job_termination.rb +9 -0
- data/lib/exel/events.rb +1 -0
- data/lib/exel/instruction.rb +3 -1
- data/lib/exel/instruction_node.rb +1 -0
- data/lib/exel/job.rb +1 -0
- data/lib/exel/listen_instruction.rb +1 -0
- data/lib/exel/logging.rb +22 -1
- data/lib/exel/logging/logger_wrapper.rb +28 -0
- data/lib/exel/logging_helper.rb +35 -0
- data/lib/exel/middleware/chain.rb +66 -0
- data/lib/exel/middleware/logging.rb +30 -0
- data/lib/exel/null_instruction.rb +1 -0
- data/lib/exel/processor_helper.rb +7 -0
- data/lib/exel/processors/async_processor.rb +2 -8
- data/lib/exel/processors/run_processor.rb +2 -7
- data/lib/exel/processors/split_processor.rb +5 -8
- data/lib/exel/providers/local_file_provider.rb +1 -0
- data/lib/exel/providers/threaded_async_provider.rb +1 -0
- data/lib/exel/sequence_node.rb +1 -0
- data/lib/exel/value.rb +1 -0
- data/lib/exel/version.rb +2 -1
- data/spec/exel/ast_node_spec.rb +24 -3
- data/spec/exel/context_spec.rb +1 -0
- data/spec/exel/deferred_context_value_spec.rb +1 -0
- data/spec/exel/events_spec.rb +1 -0
- data/spec/exel/instruction_node_spec.rb +1 -0
- data/spec/exel/instruction_spec.rb +6 -0
- data/spec/exel/job_spec.rb +1 -0
- data/spec/exel/listen_instruction_spec.rb +1 -0
- data/spec/exel/logging/logger_wrapper_spec.rb +95 -0
- data/spec/exel/logging_helper_spec.rb +25 -0
- data/spec/exel/logging_spec.rb +36 -3
- data/spec/exel/middleware/chain_spec.rb +67 -0
- data/spec/exel/middleware/logging_spec.rb +33 -0
- data/spec/exel/middleware_spec.rb +69 -0
- data/spec/exel/null_instruction_spec.rb +1 -0
- data/spec/exel/processors/async_processor_spec.rb +1 -0
- data/spec/exel/processors/run_processor_spec.rb +1 -0
- data/spec/exel/processors/split_processor_spec.rb +2 -7
- data/spec/exel/providers/local_file_provider_spec.rb +1 -0
- data/spec/exel/providers/threaded_async_provider_spec.rb +1 -0
- data/spec/exel/sequence_node_spec.rb +1 -0
- data/spec/exel/value_spec.rb +1 -0
- data/spec/exel_spec.rb +1 -0
- data/spec/integration/integration_spec.rb +1 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/integration_test_classes.rb +1 -0
- metadata +18 -18
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module EXEL
|
3
|
+
# Logging related helper methods for processors
|
4
|
+
module LoggingHelper
|
5
|
+
# @return [Logger] Returns the EXEL logger
|
6
|
+
def logger
|
7
|
+
EXEL.logger
|
8
|
+
end
|
9
|
+
|
10
|
+
# Logs a message with DEBUG severity
|
11
|
+
def log_debug(message)
|
12
|
+
logger.debug(message)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Logs a message with INFO severity
|
16
|
+
def log_info(message)
|
17
|
+
logger.info(message)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Logs a message with WARN severity
|
21
|
+
def log_warn(message)
|
22
|
+
logger.warn(message)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Logs a message with ERROR severity
|
26
|
+
def log_error(message)
|
27
|
+
logger.error(message)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Logs a message with FATAL severity
|
31
|
+
def log_fatal(message)
|
32
|
+
logger.fatal(message)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module EXEL
|
3
|
+
# Middleware is code configured to run around each processor execution. Custom middleware can be added as follows:
|
4
|
+
#
|
5
|
+
# EXEL.configure do |config|
|
6
|
+
# config.middleware.add(MyMiddleware)
|
7
|
+
# config.middleware.add(AnotherMiddleware, 'constructor arg')
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# Middleware can be any class that implements a +call+ method that includes a call to +yield+:
|
11
|
+
#
|
12
|
+
# class MyMiddleware
|
13
|
+
# def call(processor, context, args)
|
14
|
+
# puts 'before process'
|
15
|
+
# yield
|
16
|
+
# puts 'after process'
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# The +call+ method will be passed the class of the processor that will be executed, the current context, and any args
|
21
|
+
# that were passed to the processor in the job definition.
|
22
|
+
module Middleware
|
23
|
+
# Chain of middleware to be invoked in sequence around each processor execution.
|
24
|
+
class Chain
|
25
|
+
attr_reader :entries
|
26
|
+
|
27
|
+
Entry = Struct.new(:klass, :args)
|
28
|
+
|
29
|
+
def initialize
|
30
|
+
@entries = []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds a middleware class to the chain. If it is already in the chain it will be removed and added to the end.
|
34
|
+
# Any additional arguments will be passed to +new+ when the middleware is created.
|
35
|
+
def add(klass, *args)
|
36
|
+
remove(klass)
|
37
|
+
@entries << Entry.new(klass, args)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Removes a middleware class from the chain.
|
41
|
+
def remove(klass)
|
42
|
+
@entries.delete_if { |entry| entry.klass == klass }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns true if the given class is in the chain.
|
46
|
+
def include?(klass)
|
47
|
+
@entries.any? { |entry| entry.klass == klass }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Calls each middleware in the chain.
|
51
|
+
def invoke(*args)
|
52
|
+
chain = @entries.map { |entry| entry.klass.new(*entry.args) }
|
53
|
+
|
54
|
+
traverse_chain = lambda do
|
55
|
+
if chain.empty?
|
56
|
+
yield
|
57
|
+
else
|
58
|
+
chain.shift.call(*args, &traverse_chain)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
traverse_chain.call
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module EXEL
|
3
|
+
module Middleware
|
4
|
+
# Middleware to add a prefix to all messages logged during processor execution. The prefix is specified by the
|
5
|
+
# +:log_prefix+ key in the context. Also logs start, finish, and failure of the processor execution.
|
6
|
+
class Logging
|
7
|
+
def call(processor_class, context, _args, &block)
|
8
|
+
EXEL::Logging.with_prefix("#{context[:log_prefix]}[#{processor_class}] ") { log_process(&block) }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def log_process
|
14
|
+
start_time = Time.now
|
15
|
+
EXEL.logger.info 'Starting'
|
16
|
+
|
17
|
+
yield
|
18
|
+
|
19
|
+
EXEL.logger.info "Finished in #{duration(start_time)} seconds"
|
20
|
+
rescue Exception # rubocop:disable Lint/RescueException
|
21
|
+
EXEL.logger.info "Failed in #{duration(start_time)} seconds"
|
22
|
+
raise
|
23
|
+
end
|
24
|
+
|
25
|
+
def duration(start_time)
|
26
|
+
(Time.now - start_time).round(3)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -1,6 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module EXEL
|
2
3
|
# Helper methods useful to processors
|
4
|
+
# @deprecated Most functionality replaced by {EXEL::Middleware::Logging} middleware.
|
3
5
|
module ProcessorHelper
|
6
|
+
def self.included(other)
|
7
|
+
warn "DEPRECATION WARNING: [#{other}] EXEL::ProcessorHelper will be removed. For process logging, please use "\
|
8
|
+
'EXEL::Middleware::Logging instead'
|
9
|
+
end
|
10
|
+
|
4
11
|
def tag(*tags)
|
5
12
|
tags.map { |t| "[#{t}]" }.join('')
|
6
13
|
end
|
@@ -1,24 +1,18 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module EXEL
|
4
4
|
module Processors
|
5
5
|
# Implements the +async+ instruction by using the configured async provider to run a block asynchronously.
|
6
6
|
class AsyncProcessor
|
7
|
-
include EXEL::ProcessorHelper
|
8
7
|
attr_reader :provider
|
9
8
|
|
10
9
|
def initialize(context)
|
11
10
|
@context = context
|
12
11
|
@provider = EXEL.async_provider.new(context)
|
13
|
-
|
14
|
-
log_prefix_with '[AsyncProcessor]'
|
15
12
|
end
|
16
13
|
|
17
14
|
def process(block)
|
18
|
-
|
19
|
-
@provider.do_async(block)
|
20
|
-
log_info 'call to async completed'
|
21
|
-
end
|
15
|
+
@provider.do_async(block)
|
22
16
|
end
|
23
17
|
end
|
24
18
|
end
|
@@ -1,11 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
3
2
|
module EXEL
|
4
3
|
module Processors
|
5
4
|
# Implements the +run+ instruction.
|
6
5
|
class RunProcessor
|
7
|
-
include EXEL::ProcessorHelper
|
8
|
-
|
9
6
|
# Requires +context[:job]+ to contain the name of the job to be run.
|
10
7
|
def initialize(context)
|
11
8
|
@context = context
|
@@ -13,9 +10,7 @@ module EXEL
|
|
13
10
|
|
14
11
|
# Runs the specified job with the current context
|
15
12
|
def process(_block = nil)
|
16
|
-
|
17
|
-
EXEL::Job.run(@context[:job], @context)
|
18
|
-
end
|
13
|
+
EXEL::Job.run(@context[:job], @context)
|
19
14
|
end
|
20
15
|
end
|
21
16
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'csv'
|
2
3
|
require 'tempfile'
|
3
|
-
require_relative '../
|
4
|
+
require_relative '../logging_helper'
|
4
5
|
|
5
6
|
module EXEL
|
6
7
|
module Processors
|
@@ -12,7 +13,7 @@ module EXEL
|
|
12
13
|
# be deleted when splitting is complete
|
13
14
|
# * +:chunk_size+ Set to specify the number of lines that each chunk should contain
|
14
15
|
class SplitProcessor
|
15
|
-
include EXEL::
|
16
|
+
include EXEL::LoggingHelper
|
16
17
|
|
17
18
|
attr_accessor :file_name, :block
|
18
19
|
|
@@ -26,15 +27,11 @@ module EXEL
|
|
26
27
|
@context = context
|
27
28
|
@file = context[:resource]
|
28
29
|
@context[:delete_resource] = true if @context[:delete_resource].nil?
|
29
|
-
|
30
|
-
log_prefix_with '[SplitProcessor]'
|
31
30
|
end
|
32
31
|
|
33
32
|
def process(callback)
|
34
|
-
|
35
|
-
|
36
|
-
finish(callback)
|
37
|
-
end
|
33
|
+
process_file(callback)
|
34
|
+
finish(callback)
|
38
35
|
end
|
39
36
|
|
40
37
|
def process_line(line, callback)
|
data/lib/exel/sequence_node.rb
CHANGED
data/lib/exel/value.rb
CHANGED
data/lib/exel/version.rb
CHANGED
data/spec/exel/ast_node_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module EXEL
|
2
3
|
describe ASTNode do
|
3
4
|
let(:context) { instance_double(EXEL::Context) }
|
@@ -9,13 +10,33 @@ module EXEL
|
|
9
10
|
TestNode = Class.new(ASTNode)
|
10
11
|
|
11
12
|
describe '#start' do
|
12
|
-
context 'when
|
13
|
-
|
14
|
-
|
13
|
+
context 'when a JobTermination error bubbles up' do
|
14
|
+
let(:node) { TestNode.new(instruction) }
|
15
|
+
|
16
|
+
before do
|
15
17
|
allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination, 'Error')
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'ensures the process fails silently' do
|
16
21
|
expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
|
17
22
|
expect { node.start(context) }.not_to raise_error
|
18
23
|
end
|
24
|
+
|
25
|
+
it 'logs the error by default' do
|
26
|
+
expect(EXEL.logger).to receive(:error).with('JobTerminationError: Error')
|
27
|
+
node.start(context)
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'given a log instruction' do
|
31
|
+
before do
|
32
|
+
allow(node).to receive(:run).and_raise(EXEL::Error::JobTermination.new('Error', :warn))
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'logs the error with the given cmd' do
|
36
|
+
expect(EXEL.logger).to receive(:warn).with('JobTerminationError: Error')
|
37
|
+
node.start(context)
|
38
|
+
end
|
39
|
+
end
|
19
40
|
end
|
20
41
|
end
|
21
42
|
|
data/spec/exel/context_spec.rb
CHANGED
data/spec/exel/events_spec.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module EXEL
|
2
3
|
describe Instruction do
|
3
4
|
subject(:instruction) { EXEL::Instruction.new(processor_class, args) }
|
@@ -14,6 +15,11 @@ module EXEL
|
|
14
15
|
instruction.execute(context)
|
15
16
|
end
|
16
17
|
|
18
|
+
it 'invokes the middleware chain' do
|
19
|
+
expect(EXEL.middleware).to receive(:invoke).with(processor_class, context, args)
|
20
|
+
instruction.execute(context)
|
21
|
+
end
|
22
|
+
|
17
23
|
it 'does not pass a copy of the context' do
|
18
24
|
allow(processor_class).to receive(:new) do |context_arg|
|
19
25
|
expect(context_arg).to be(context)
|
data/spec/exel/job_spec.rb
CHANGED
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module EXEL
|
3
|
+
module Logging
|
4
|
+
describe LoggerWrapper do
|
5
|
+
subject(:wrapper) { LoggerWrapper.new(logger) }
|
6
|
+
let(:logger) { instance_double(Logger) }
|
7
|
+
|
8
|
+
it { is_expected.to be_a(SimpleDelegator) }
|
9
|
+
|
10
|
+
LOG_LEVELS = %i(debug info warn error fatal unknown).freeze
|
11
|
+
|
12
|
+
context 'without a Logging prefix' do
|
13
|
+
LOG_LEVELS.each do |level|
|
14
|
+
describe "##{level}" do
|
15
|
+
context 'when passed a message string' do
|
16
|
+
it 'passes the message to its wrapped logger' do
|
17
|
+
expect(logger).to receive(level).with('message')
|
18
|
+
wrapper.send(level, 'message')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'when passed a message block' do
|
23
|
+
it 'passes the block to its wrapped logger' do
|
24
|
+
block = proc {}
|
25
|
+
expect(logger).to receive(level).with(nil, &block)
|
26
|
+
|
27
|
+
wrapper.send(level, &block)
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'and a progname' do
|
31
|
+
it 'passes the block and progname to its wrapped logger' do
|
32
|
+
block = proc {}
|
33
|
+
expect(logger).to receive(level).with('test', &block)
|
34
|
+
|
35
|
+
wrapper.send(level, 'test', &block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#add' do
|
43
|
+
it 'passes the message to its wrapped logger' do
|
44
|
+
expect(logger).to receive(:add).with(Logger::FATAL, 'message', 'progname')
|
45
|
+
wrapper.add(Logger::FATAL, 'message', 'progname')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'with a Logging prefix' do
|
51
|
+
before { allow(Logging).to receive(:prefix).and_return('prefix: ') }
|
52
|
+
|
53
|
+
LOG_LEVELS.each do |level|
|
54
|
+
describe "##{level}" do
|
55
|
+
context 'when passed a message string' do
|
56
|
+
it 'passes the prefixed message to its wrapped logger' do
|
57
|
+
expect(logger).to receive(level).with('prefix: message')
|
58
|
+
wrapper.send(level, 'message')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when passed a message block' do
|
63
|
+
it 'passes the prefixed block to its wrapped logger' do
|
64
|
+
expect(logger).to receive(level) do |progname, &block|
|
65
|
+
expect(progname).to be_nil
|
66
|
+
expect(block.call).to eq('prefix: message')
|
67
|
+
end
|
68
|
+
|
69
|
+
wrapper.send(level) { 'message' }
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'and a progname' do
|
73
|
+
it 'passes the prefixed block and progname to its wrapped logger' do
|
74
|
+
expect(logger).to receive(level) do |progname, &block|
|
75
|
+
expect(progname).to eq('test')
|
76
|
+
expect(block.call).to eq('prefix: message')
|
77
|
+
end
|
78
|
+
|
79
|
+
wrapper.send(level, 'test') { 'message' }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#add' do
|
87
|
+
it 'passes the prefixed message to its wrapped logger' do
|
88
|
+
expect(logger).to receive(:add).with(Logger::FATAL, 'prefix: message', 'progname')
|
89
|
+
wrapper.add(Logger::FATAL, 'message', 'progname')
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|