logstash-filter-fix_protocol 0.1.3 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bbf914880612f207ca4535d38a6adbba00d75713
4
- data.tar.gz: 1de673ca2eb056627156f0984df6a7a4ac41ec9f
3
+ metadata.gz: 9c738b5a16d38b8c62fb2c1f03a6656af33e9ca8
4
+ data.tar.gz: 54367647f6258f5c17ad2739868ca855f5432439
5
5
  SHA512:
6
- metadata.gz: 79be0dc6622bd4bca51a524443b40414b07c581cca300c38104010c794a114175b1274af517e90bb024b6ac73fba1f725b6ad4ee719318ec241db651c1c2122b
7
- data.tar.gz: b5ac803915f4886257ed3849f02887521ccbb5b39f5bd3123edebfcbcd96f96727481a3ad7abee64532fb35359a1ce00fcad4eba8a90ec5ab10dda3ac3c77002
6
+ metadata.gz: 86c6277a75cce71efa8f413f887e30669bc4954aeba5acd9ce52d831272ab68215d103ea69e0ec8b950c8bf2198a099f8edbe30efeea18c283769b68c4b66015
7
+ data.tar.gz: c24afff68ef9a9bcf109a796aba773b6c17a5c0dfb4fcf3fd91d5928c0428e5d8e2d332795815d523db697d415e61059888bf6f7a75ce7959242708213d386cb
@@ -1,17 +1,16 @@
1
1
  require 'quickfix'
2
- require 'active_support/core_ext'
3
2
 
4
3
  module LogStash
5
4
  module Filters
6
5
  class FixMessage < quickfix.Message
7
- attr_reader :type, :msg_string, :session_dictionary, :data_dictionary, :all_dictionaries
6
+ attr_reader :type, :msg_string, :session_dictionary, :data_dictionary, :all_dictionaries, :unknown_fields
8
7
 
9
8
  def initialize(msg_string, data_dictionary, session_dictionary)
10
9
  @session_dictionary = session_dictionary
11
10
  @data_dictionary = data_dictionary
12
11
  @msg_string = msg_string
13
12
  @type = quickfix.MessageUtils.getMessageType(msg_string)
14
-
13
+ @unknown_fields = []
15
14
  @all_dictionaries = [@data_dictionary, @session_dictionary]
16
15
 
17
16
  super(msg_string, data_dictionary, false)
@@ -44,6 +43,14 @@ module LogStash
44
43
  value = dd.get_field_name(tag)
45
44
  return value if value.present?
46
45
  end
46
+
47
+ tag = to_string(tag)
48
+ @unknown_fields << tag
49
+ tag
50
+ end
51
+
52
+ def to_string(tag)
53
+ tag.to_s.force_encoding("UTF-8")
47
54
  end
48
55
 
49
56
  def field_map_to_hash(field_map, msg_type = nil)
@@ -56,17 +63,21 @@ module LogStash
56
63
  tag = field.get_tag
57
64
  value = field.get_value
58
65
 
59
- # IF GROUP
60
66
  if msg_type.present? and @data_dictionary.is_group(msg_type, tag)
61
67
  groups = []
62
68
 
63
69
  for i in 1..value.to_i
64
- group_map = field_map.get_group(i, tag)
65
- groups << field_map_to_hash(group_map, msg_type)
70
+ begin
71
+ group_map = field_map.get_group(i, tag)
72
+ group_hash = field_map_to_hash(group_map, msg_type)
73
+ rescue Java::Quickfix::FieldNotFound
74
+ group_hash = {to_string(tag) => i}
75
+ self.unknown_fields << to_string(tag)
76
+ end
77
+ groups << group_hash
66
78
  end
67
79
 
68
80
  value = groups
69
- # IF FIELD
70
81
  elsif @data_dictionary.is_field(tag)
71
82
  value =
72
83
  case field_type(tag)
@@ -3,6 +3,7 @@ require "logstash/filters/base"
3
3
  require "logstash/namespace"
4
4
  require "logstash/filters/data_dictionary"
5
5
  require "logstash/filters/fix_message"
6
+ require 'active_support/core_ext'
6
7
 
7
8
  module LogStash
8
9
  module Filters
@@ -48,18 +49,15 @@ module LogStash
48
49
  if fix_message
49
50
  fix_hash = fix_message.to_hash
50
51
 
52
+ # TODO: Unknown tags are only set after calling #to_hash
53
+ # this creates an implicit timing issue
54
+ if fix_message.unknown_fields.any?
55
+ event["unknown_fields"] = fix_message.unknown_fields
56
+ event["tags"] = ["_fix_field_not_found"]
57
+ end
58
+
51
59
  fix_hash.each do |key, value|
52
- begin
53
- event[key] = value
54
- rescue NoMethodError => e
55
- puts "********"
56
- puts "WARNING: Could not correctly parse #{event["fix_message"]}"
57
- puts JSON.pretty_generate(fix_hash)
58
- puts "Message: #{e.message}"
59
- puts "********"
60
- ensure
61
- next
62
- end
60
+ event[key] = value
63
61
  end
64
62
  end
65
63
  end
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "logstash-filter-fix_protocol"
7
- s.version = "0.1.3"
7
+ s.version = "0.2.0"
8
8
  s.authors = ["Connamara Systems"]
9
9
  s.email = ["info@connamara.com"]
10
10
 
@@ -9,6 +9,9 @@ describe LF::FixMessage do
9
9
  let(:message) { LF::FixMessage.new(message_str, data_dictionary, session_dictionary) }
10
10
  let(:message2) { LF::FixMessage.new(another_str, data_dictionary, session_dictionary) }
11
11
 
12
+ let(:fix_4) { {data_dictionary: "FIX42.xml", session_dictionary: nil} }
13
+ let(:fix_5) { {data_dictionary: "FIX50SP1.xml", session_dictionary: "FIXT11.xml"} }
14
+
12
15
  describe '#to_hash' do
13
16
  it 'converts the FIX message string to a hash in human readable format' do
14
17
  expect(message.to_hash).to eq({
@@ -59,10 +62,41 @@ describe LF::FixMessage do
59
62
  end
60
63
  end
61
64
 
65
+ describe '#field_map_to_hash' do
66
+ context 'invalid message - field name not found' do
67
+ let(:fix_string) { "8=FIX.4.29=24035=834=649=DUMMY_INC52=20150826-23:10:17.74456=ANOTHER_INC57=Firm_B1=Inst_B6=011=best_buy14=517=ITRZ1201508261_2420=022=831=101032=537=ITRZ1201508261_1238=539=27012=22740=241=best_buy44=101154=155=ITRZ160=20150826-23:10:15.547150=2151=010=227" }
68
+
69
+ it 'adds an error object and uses the data dictionary index number as the key' do
70
+ data_dictionary = LF::DataDictionary.new(load_fixture(fix_5[:data_dictionary]))
71
+ sess_dictionary = LF::DataDictionary.new(load_fixture(fix_5[:session_dictionary]))
72
+
73
+ fix_message = LF::FixMessage.new(fix_string, data_dictionary, sess_dictionary)
74
+ hash = fix_message.to_hash
75
+ # Side Note: field 20, LastShares, was changed between FIX 4 / 5
76
+ expect(hash["20"]).to eq("0")
77
+ expect(hash["7012"]).to eq("227")
78
+ expect(fix_message.unknown_fields.include?("20")).to be true
79
+ expect(fix_message.unknown_fields.include?("7012")).to be true
80
+ end
81
+ end
82
+
83
+ context 'invalid message - group field name not found (Java::Quickfix::FieldNotFound)' do
84
+ let(:fix_string) { "8=FIX.4.235=D34=249=TW52=<TIME>56=ISLD11=ID21=140=154=138=200.0055=INTC386=3336=PRE-OPEN336=AFTER-HOURS60=<TIME>" }
85
+
86
+ it 'adds an error object and uses the data dictionary index number as the key' do
87
+ data_dictionary = LF::DataDictionary.new(load_fixture(fix_5[:data_dictionary]))
88
+ sess_dictionary = LF::DataDictionary.new(load_fixture(fix_5[:session_dictionary]))
89
+
90
+ fix_message = LF::FixMessage.new(fix_string, data_dictionary, sess_dictionary)
91
+ hash = fix_message.to_hash
92
+ expect(fix_message.unknown_fields.include?("386")).to be true
93
+ expect(hash["NoTradingSessions"]).to eq([{"TradingSessionID"=>"PRE-OPEN"}, {"TradingSessionID"=>"AFTER-HOURS"}, {"386"=>3}])
94
+ end
95
+ end
96
+ end
97
+
62
98
  context 'message types' do
63
- let(:fix_4) { {data_dictionary: "FIX42.xml", session_dictionary: nil} }
64
- let(:fix_5) { {data_dictionary: "FIX50SP1.xml", session_dictionary: "FIXT11.xml"} }
65
- # data is from: http://fixparser.targetcompid.com/
99
+ # most data is from: http://fixparser.targetcompid.com/
66
100
  context 'heartbeats' do
67
101
  it 'can parse these' do
68
102
  [fix_4, fix_5].each do |version|
@@ -88,8 +122,7 @@ describe LF::FixMessage do
88
122
  expect(["Logon", "LOGON"].include?(hash["MsgType"])).to be true
89
123
 
90
124
  expect(hash["BeginString"]).to be_a String
91
- # NOTE: This field was changed between FIX 4 / 5
92
- # expect([String, Fixnum].include?(hash["HeartBtInt"].class)).to be true
125
+ expect([String, Fixnum].include?(hash["HeartBtInt"].class)).to be true
93
126
 
94
127
  expect(["BANZAI", "EXEC"].include?(hash["TargetCompID"])).to be true
95
128
  expect(["BANZAI", "EXEC"].include?(hash["SenderCompID"])).to be true
@@ -36,14 +36,31 @@ describe LF::FixProtocol do
36
36
  end
37
37
  end
38
38
 
39
+ context 'an incoming execution report' do
40
+ config fix_4_configuration
41
+
42
+ execution = "8=FIXT.1.1\x0135=8\x0149=ITG\x0156=SILO\x01315=8\x016=100.25\x01410=50.25\x01424=23.45\x01411=Y\x0143=N\x0140=1\x015=N\x01"
43
+
44
+ sample(execution) do
45
+ filtered_event = subject
46
+ insist { filtered_event["BeginString"] } == "FIXT.1.1"
47
+ insist { filtered_event["MsgType"] } == "ExecutionReport"
48
+ insist { filtered_event["SenderCompID"] } == "ITG"
49
+ insist { filtered_event["AvgPx"] } == 100.25
50
+ insist { filtered_event["OrdType"] } == "MARKET"
51
+ insist { filtered_event["UnderlyingPutOrCall"] } == 8
52
+ end
53
+ end
54
+
39
55
  context 'invalid message - Java::Quickfix::InvalidMessage' do
40
56
  config fix_4_configuration
41
57
 
42
58
  invalid_msg = "8=invalid_stuff"
43
59
 
44
60
  sample(invalid_msg) do
45
- insist { subject["tags"] } == ["_fix_parse_failure"]
46
- insist { subject["message"] } == invalid_msg
61
+ filtered_event = subject
62
+ insist { filtered_event["tags"] } == ["_fix_parse_failure"]
63
+ insist { filtered_event["message"] } == invalid_msg
47
64
  end
48
65
  end
49
66
 
@@ -53,41 +70,48 @@ describe LF::FixProtocol do
53
70
  invalid_msg = "8=FIX.4.09=8135=D34=349garbled=TW52=<TIME>56=ISLD11=ID21=340=154=155=INTC10=0"
54
71
 
55
72
  sample(invalid_msg) do
56
- insist { subject["tags"] } == ["_fix_parse_failure"]
57
- insist { subject["message"] } == invalid_msg
73
+ filtered_event = subject
74
+ insist { filtered_event["tags"] } == ["_fix_parse_failure"]
75
+ insist { filtered_event["message"] } == invalid_msg
58
76
  end
59
77
  end
60
78
 
61
- context 'an incoming execution report' do
79
+ context 'invalid message - group field not found - Java::Quickfix::FieldNotFound' do
62
80
  config fix_4_configuration
63
81
 
64
- execution = "8=FIXT.1.1\x0135=8\x0149=ITG\x0156=SILO\x01315=8\x016=100.25\x01410=50.25\x01424=23.45\x01411=Y\x0143=N\x0140=1\x015=N\x01"
82
+ invalid_msg = "8=FIX.4.235=D34=249=TW52=<TIME>56=ISLD11=ID21=140=154=138=200.0055=INTC386=3336=PRE-OPEN336=AFTER-HOURS60=<TIME>"
65
83
 
66
- sample(execution) do
84
+ sample(invalid_msg) do
67
85
  filtered_event = subject
68
- insist { filtered_event["BeginString"] } == "FIXT.1.1"
69
- insist { filtered_event["MsgType"] } == "ExecutionReport"
70
- insist { filtered_event["SenderCompID"] } == "ITG"
71
- insist { filtered_event["AvgPx"] } == 100.25
72
- insist { filtered_event["OrdType"] } == "MARKET"
73
- insist { filtered_event["UnderlyingPutOrCall"] } == 8
86
+ # adds a tag to warn that fields weren't found in the DD
87
+ insist { filtered_event["tags"] } == ["_fix_field_not_found"]
88
+ insist { filtered_event["unknown_fields"] } == ["386"]
89
+ # correctly parses as much as it can
90
+ insist { filtered_event["OrderQty"]} == 200.0
91
+ insist { filtered_event["OrdType"]} == "MARKET"
92
+ insist { filtered_event["Side"]} == "BUY"
93
+ insist { filtered_event["Symbol"]} == "INTC"
94
+ insist { filtered_event["NoTradingSessions"]} == [{"TradingSessionID"=>"PRE-OPEN"}, {"TradingSessionID"=>"AFTER-HOURS"}, {"386"=>3}] # here's the rescued group field
74
95
  end
75
96
  end
76
97
 
77
- context 'it removes unparseable key-value pairs' do
98
+ context 'invalid message - field name not found' do
78
99
  config fix_5_configuration
79
100
 
80
101
  execution = "8=FIX.4.2\x019=240\x0135=8\x0134=6\x0149=DUMMY_INC\x0152=20150826-23:10:17.744\x0156=ANOTHER_INC\x0157=Firm_B\x011=Inst_B\x016=0\x0111=151012569\x0117=ITRZ1201508261_24\x0120=0\x0122=8\x0131=1010\x0132=5\x0137=ITRZ1201508261_12\x0138=5\x0139=2\x0140=2\x0141=best_buy\x0144=1011\x0154=1\x0155=ITRZ1\x0160=20150826-23:10:15.547\x01150=2\x01151=0\x0110=227\x01"
81
102
 
82
103
  sample(execution) do
83
- expect { subject }.to output.to_stdout
84
104
  filtered_event = subject
105
+ # adds a tag to warn that fields weren't found in the DD
106
+ insist { filtered_event["tags"] } == ["_fix_field_not_found"]
107
+ insist { filtered_event["unknown_fields"] } == ["20"]
108
+ # correctly parses as much as it can
85
109
  insist { filtered_event["BeginString"] } == "FIX.4.2"
86
110
  insist { filtered_event["MsgType"] } == "ExecutionReport"
87
111
  insist { filtered_event["SenderCompID"] } == "DUMMY_INC"
88
112
  insist { filtered_event["AvgPx"] } == 0.0
89
113
  insist { filtered_event["OrdType"] } == "LIMIT"
90
- insist { filtered_event["LeavesQty"] } == 0.0 # this should fail if parsing gets rescued, but doesnt finish setting on the event object
114
+ insist { filtered_event["LeavesQty"] } == 0.0
91
115
  end
92
116
  end
93
117
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-fix_protocol
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Connamara Systems
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-02 00:00:00.000000000 Z
11
+ date: 2016-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement