logstash-filter-fix_protocol 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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