logstash-codec-protobuf 1.0.5 → 1.1.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
- SHA1:
3
- metadata.gz: a623045ad79469eeefd501735a5fc3284c19eef3
4
- data.tar.gz: 1a4b69f9ae131f5abe2b55270073c975edcbeab3
2
+ SHA256:
3
+ metadata.gz: c7759d64f37dcbb075892f198b46795d1b35964b66341e70107d13a9065de70b
4
+ data.tar.gz: db8bfbab2b9cdd7f3dbdbdaa8c0f8b12ab46b7ae4ef024cbc0b35706f3dc08e5
5
5
  SHA512:
6
- metadata.gz: 0e152a7b9675dbd1c430da257a340364944f71b430bd093913705f376971650cc46933395a492f38732dc3665472ca56ebdc3e995d0c5023236b67466a7dc835
7
- data.tar.gz: a55114ffbc512e1ee31748093f864c1683e33536f590ded503cbae5ca2ec4f61754e7d518efc722593c9af4040270438f40fa05d3fa03ae27e334d1a61ff520c
6
+ metadata.gz: 72147c0788aed161306b74f8537259b04c615313c85d803e51eca96abdfd0bfdee2fb0f415fbd2eb56e66f08e69a46e8e43403175966ec9178dcfa0133e90e4c
7
+ data.tar.gz: 1872037c0dff0b05cfaba1d9ac6fe663b12d8130cdab8931d5a3e8b4fc03af9b48720120835bf5acd941a1c0814276a797f9f7a1081ee0d5e4bdfdc63d444fab
data/CHANGELOG.md CHANGED
@@ -1,4 +1,4 @@
1
- ## 1.0.5
1
+ ## 1.1.0
2
2
  - Add support for protobuf3
3
3
 
4
4
  ## 1.0.4
data/README.md CHANGED
@@ -4,51 +4,74 @@ This is a codec plugin for [Logstash](https://github.com/elastic/logstash) to pa
4
4
 
5
5
  # Prerequisites and Installation
6
6
 
7
- * prepare your ruby versions of the protobuf definitions, for example using the ruby-protoc compiler from https://github.com/codekitchen/ruby-protocol-buffers
7
+ * prepare your ruby versions of the protobuf definitions
8
+ ** For protobuf 2 use the [ruby-protoc compiler](https://github.com/codekitchen/ruby-protocol-buffers).
9
+ ** For protobuf 3 use the [official google protobuf compiler](https://developers.google.com/protocol-buffers/docs/reference/ruby-generated).
8
10
  * install the codec: `bin/logstash-plugin install logstash-codec-protobuf`
9
11
  * use the codec in your logstash config file. See details below.
10
12
 
11
13
  ## Configuration
12
14
 
13
- include_path (required): an array of strings with filenames or directory names where logstash can find your protobuf definitions. Please provide absolute paths. For directories it will only try to import files ending on .rb
15
+ include_path (required): an array of strings with filenames where logstash can find your protobuf definitions. Please provide absolute paths. For directories it will only try to import files ending on .rb
14
16
 
15
- class_name (required): the name of the protobuf class that is to be decoded or encoded.
17
+ class_name (required): the name of the protobuf class that is to be decoded or encoded. For protobuf 2 separate the modules with ::. For protobuf 3 use single dots. See examples below.
18
+
19
+ protobuf_version (optional): set this to 3 if you want to use protobuf 3 definitions. Defaults to 2.
16
20
 
17
21
  ## Usage example: decoder
18
22
 
19
23
  Use this as a codec in any logstash input. Just provide the name of the class that your incoming objects will be encoded in, and specify the path to the compiled definition.
20
- Here's an example for a kafka input:
24
+ Here's an example for a kafka input with protobuf 2:
25
+
26
+ kafka
27
+ {
28
+ zk_connect => "127.0.0.1"
29
+ topic_id => "unicorns_protobuffed"
30
+ codec => protobuf
31
+ {
32
+ class_name => "Animals::Unicorn"
33
+ include_path => ['/path/to/pb_definitions/Animal.pb.rb', '/path/to/pb_definitions/Unicorn.pb.rb']
34
+ }
35
+ }
36
+
37
+ Example for protobuf 3:
21
38
 
22
39
  kafka
23
- {
24
- zk_connect => "127.0.0.1"
25
- topic_id => "unicorns_protobuffed"
26
- codec => protobuf
27
- {
28
- class_name => "Unicorn"
29
- include_path => ['/my/path/to/compiled/protobuf/definitions/UnicornProtobuf.pb.rb']
30
- }
31
- }
40
+ {
41
+ zk_connect => "127.0.0.1"
42
+ topic_id => "unicorns_protobuffed"
43
+ codec => protobuf
44
+ {
45
+ class_name => "Animals.Unicorn"
46
+ include_path => ['/path/to/pb_definitions/Animal_pb.rb', '/path/to/pb_definitions/Unicorn_pb.rb']
47
+ protobuf_version => 3
48
+ }
49
+ }
32
50
 
33
- ### Example with referenced definitions
51
+ ### Class loading order
34
52
 
35
- Imagine you have the following protobuf relationship: class Cheese lives in namespace Foods::Dairy and uses another class Milk.
53
+ Imagine you have the following protobuf version 2 relationship: class Unicorn lives in namespace Animal::Horse and uses another class Wings.
36
54
 
37
- module Foods
38
- module Dairy
39
- class Cheese
40
- set_fully_qualified_name "Foods.Dairy.Cheese"
41
- optional ::Foods::Cheese::Milk, :milk, 1
42
- optional :int64, :unique_id, 2
43
- # here be more field definitions
55
+ module Animal
56
+ module Horse
57
+ class Unicorn
58
+ set_fully_qualified_name "Animal.Horse.Unicorn"
59
+ optional ::Animal::Bodypart::Wings, :wings, 1
60
+ optional :string, :name, 2
61
+ # here be more field definitions
44
62
 
45
- Make sure to put the referenced Milk class first in the include_path:
63
+ Make sure to put the referenced wings class first in the include_path:
46
64
 
47
- include_path => ['/path/to/protobuf/definitions/Milk.pb.rb','/path/to/protobuf/definitions/Cheese.pb.rb']
65
+ include_path => ['/path/to/pb_definitions/wings.pb.rb','/path/to/pb_definitions/unicorn.pb.rb']
48
66
 
49
67
  Set the class name to the parent class:
50
68
 
51
- class_name => "Foods::Dairy::Cheese"
69
+ class_name => "Animal::Horse::Unicorn"
70
+
71
+ for protobuf 2. For protobuf 3 use
72
+
73
+ class_name => "Animal.Horse.Unicorn"
74
+
52
75
 
53
76
  ## Usage example: encoder
54
77
 
@@ -59,14 +82,18 @@ The configuration of the codec for encoding logstash events for a protobuf outpu
59
82
 
60
83
  ## Troubleshooting
61
84
 
62
- ### "uninitialized constant SOME_CLASS_NAME"
85
+ ### Protobuf 2
86
+ #### "uninitialized constant SOME_CLASS_NAME"
63
87
 
64
88
  If you include more than one definition class, consider the order of inclusion. This is especially relevant if you include whole directories. A definition might refer to another definition that is not loaded yet. In this case, please specify the files in the include_path variable in reverse order of reference. See 'Example with referenced definitions' above.
65
89
 
66
- ### no protobuf output
90
+ #### no protobuf output
91
+
92
+ Maybe your protobuf definition does not fullfill the requirements and needs additional fields. Run logstash with the --debug flag and search for error messages.
67
93
 
68
- Maybe your protobuf definition does not fullfill the requirements and needs additional fields. Run logstash with the --debug flag and grep for "error 2".
94
+ ### Protobuf 3
69
95
 
96
+ Tba.
70
97
 
71
98
  ## Limitations and roadmap
72
99
 
@@ -8,7 +8,7 @@ require 'protocol_buffers' # https://github.com/codekitchen/ruby-protocol-buffer
8
8
  #
9
9
  # Requires the protobuf definitions as ruby files. You can create those using the [ruby-protoc compiler](https://github.com/codekitchen/ruby-protocol-buffers).
10
10
  #
11
- # The following shows a usage example for decoding events from a kafka stream:
11
+ # The following shows a usage example for decoding protobuf 2 encoded events from a kafka stream:
12
12
  # [source,ruby]
13
13
  # kafka
14
14
  # {
@@ -21,21 +21,39 @@ require 'protocol_buffers' # https://github.com/codekitchen/ruby-protocol-buffer
21
21
  # }
22
22
  # }
23
23
  #
24
+ # Same example for protobuf 3:
25
+ # [source,ruby]
26
+ # kafka
27
+ # {
28
+ # zk_connect => "127.0.0.1"
29
+ # topic_id => "your_topic_goes_here"
30
+ # codec => protobuf
31
+ # {
32
+ # class_name => "Animal.Unicorn"
33
+ # include_path => ['/path/to/protobuf/definitions/UnicornProtobuf_pb.rb']
34
+ # protobuf_version => 3
35
+ # }
36
+ # }
37
+ #
24
38
 
25
39
  class LogStash::Codecs::Protobuf < LogStash::Codecs::Base
26
40
  config_name 'protobuf'
27
41
 
28
42
  # Name of the class to decode.
29
- # If your protobuf definition contains modules, prepend them to the class name with double colons like so:
43
+ # If your protobuf 2 definition contains modules, prepend them to the class name with double colons like so:
30
44
  # [source,ruby]
31
- # class_name => "Foods::Dairy::Cheese"
45
+ # class_name => "Animal::Horse::Unicorn"
32
46
  #
33
47
  # This corresponds to a protobuf definition starting as follows:
34
48
  # [source,ruby]
35
- # module Foods
36
- # module Dairy
37
- # class Cheese
38
- # # here are your field definitions.
49
+ # module Animal
50
+ # module Horse
51
+ # class Unicorn
52
+ # # here are your field definitions.
53
+ #
54
+ # For protobuf 3 separate the modules with single dots.
55
+ # [source,ruby]
56
+ # class_name => "Animal.Horse.Unicorn"
39
57
  #
40
58
  # If your class references other definitions: you only have to add the main class here.
41
59
  config :class_name, :validate => :string, :required => true
@@ -44,19 +62,19 @@ class LogStash::Codecs::Protobuf < LogStash::Codecs::Base
44
62
  # When using more than one file, make sure to arrange the files in reverse order of dependency so that each class is loaded before it is
45
63
  # refered to by another.
46
64
  #
47
- # Example: a class _Cheese_ referencing another protobuf class _Milk_
65
+ # Example: a class _Unicorn_ referencing another protobuf class _Wings_
48
66
  # [source,ruby]
49
- # module Foods
50
- # module Dairy
51
- # class Cheese
52
- # set_fully_qualified_name "Foods.Dairy.Cheese"
53
- # optional ::Foods::Cheese::Milk, :milk, 1
54
- # optional :int64, :unique_id, 2
55
- # # here be more field definitions
67
+ # module Animal
68
+ # module Horse
69
+ # class Unicorn
70
+ # set_fully_qualified_name "Animal.Horse.Unicorn"
71
+ # optional ::Animal::Bodypart::Wings, :wings, 1
72
+ # optional :string, :name, 2
73
+ # # here be more field definitions
56
74
  #
57
75
  # would be configured as
58
76
  # [source,ruby]
59
- # include_path => ['/path/to/protobuf/definitions/Milk.pb.rb','/path/to/protobuf/definitions/Cheese.pb.rb']
77
+ # include_path => ['/path/to/protobuf/definitions/Wings.pb.rb','/path/to/protobuf/definitions/Unicorn.pb.rb']
60
78
  #
61
79
  # When using the codec in an output plugin:
62
80
  # * make sure to include all the desired fields in the protobuf definition, including timestamp.
@@ -66,17 +84,20 @@ class LogStash::Codecs::Protobuf < LogStash::Codecs::Base
66
84
  #
67
85
  config :include_path, :validate => :array, :required => true
68
86
 
69
- # Protocol buffer version switch. Set to false (default) for version 2. Please note that the behaviour for enums varies between the versions.
87
+ # Protocol buffer version switch. Defaults to version 2. Please note that the behaviour for enums varies between the versions.
70
88
  # For protobuf 2 you will get integer representations for enums, for protobuf 3 you'll get string representations due to a different converter library.
71
89
  # Recommendation: use the translate plugin to restore previous behaviour when upgrading.
72
- config :protobuf_version_3, :validate => :boolean, :required => true, :default=>false
90
+ config :protobuf_version, :validate => [2,3], :default => 2, :required => true
73
91
 
92
+ # To tolerate faulty messages that cannot be decoded, set this to false. Otherwise the pipeline will stop upon encountering a non decipherable message.
93
+ config :stop_on_error, :validate => :boolean, :default => false, :required => false
74
94
 
75
95
  def register
76
96
  @metainfo_messageclasses = {}
77
97
  @metainfo_enumclasses = {}
98
+ @metainfo_pb2_enumlist = []
78
99
  include_path.each { |path| load_protobuf_definition(path) }
79
- if @protobuf_version_3
100
+ if @protobuf_version == 3
80
101
  @pb_builder = Google::Protobuf::DescriptorPool.generated_pool.lookup(class_name).msgclass
81
102
  else
82
103
  @pb_builder = pb2_create_instance(class_name)
@@ -85,24 +106,24 @@ class LogStash::Codecs::Protobuf < LogStash::Codecs::Base
85
106
 
86
107
 
87
108
  def decode(data)
88
- begin
89
- if @protobuf_version_3
90
- decoded = @pb_builder.decode(data.to_s)
91
- h = pb3_deep_to_hash(decoded)
92
- else
93
- decoded = @pb_builder.parse(data.to_s)
94
- h = decoded.to_hash
95
- end
96
- yield LogStash::Event.new(h) if block_given?
97
- rescue => e
98
- @logger.warn("Couldn't decode protobuf: #{e.inspect}.")
109
+ if @protobuf_version == 3
110
+ decoded = @pb_builder.decode(data.to_s)
111
+ h = pb3_deep_to_hash(decoded)
112
+ else
113
+ decoded = @pb_builder.parse(data.to_s)
114
+ h = decoded.to_hash
115
+ end
116
+ yield LogStash::Event.new(h) if block_given?
117
+ rescue => e
118
+ @logger.warn("Couldn't decode protobuf: #{e.inspect}.")
119
+ if stop_on_error
99
120
  raise e
100
121
  end
101
122
  end # def decode
102
123
 
103
124
 
104
125
  def encode(event)
105
- if @protobuf_version_3
126
+ if @protobuf_version == 3
106
127
  protobytes = pb3_encode_wrapper(event)
107
128
  else
108
129
  protobytes = pb2_encode_wrapper(event)
@@ -113,22 +134,23 @@ class LogStash::Codecs::Protobuf < LogStash::Codecs::Base
113
134
 
114
135
  private
115
136
  def pb3_deep_to_hash(input)
116
- if input.class.ancestors.include? Google::Protobuf::MessageExts # it's a protobuf class
137
+ case input
138
+ when Google::Protobuf::MessageExts # it's a protobuf class
117
139
  result = Hash.new
118
140
  input.to_hash.each {|key, value|
119
141
  result[key] = pb3_deep_to_hash(value) # the key is required for the class lookup of enums.
120
142
  }
121
- elsif input.kind_of?(Array)
143
+ when ::Array
122
144
  result = []
123
145
  input.each {|value|
124
146
  result << pb3_deep_to_hash(value)
125
147
  }
126
- elsif input.kind_of?(::Hash)
148
+ when ::Hash
127
149
  result = {}
128
150
  input.each {|key, value|
129
151
  result[key] = pb3_deep_to_hash(value)
130
152
  }
131
- elsif input.instance_of? Symbol # is an Enum
153
+ when Symbol # is an Enum
132
154
  result = input.to_s.sub(':','')
133
155
  else
134
156
  result = input
@@ -137,65 +159,66 @@ class LogStash::Codecs::Protobuf < LogStash::Codecs::Base
137
159
  end
138
160
 
139
161
  def pb3_encode_wrapper(event)
140
- begin
141
- data = pb3_encode(event.to_hash, @class_name)
142
- pb_obj = @pb_builder.new(data)
143
- @pb_builder.encode(pb_obj)
144
- rescue ArgumentError => e
145
- @logger.debug("Encoding error 2. Probably mismatching protobuf definition. Required fields in the protobuf definition are: " + event.to_hash.keys.join(", ") + " and the timestamp field name must not include a @. ")
146
- raise e
147
- rescue => e
148
- @logger.debug("Couldn't generate protobuf: ${e}")
149
- raise e
150
- end
162
+ data = pb3_encode(event.to_hash, @class_name)
163
+ pb_obj = @pb_builder.new(data)
164
+ @pb_builder.encode(pb_obj)
165
+ rescue ArgumentError => e
166
+ k = event.to_hash.keys.join(", ")
167
+ @logger.debug("Encoding error 2. Probably mismatching protobuf definition. Required fields in the protobuf definition are: #{k} and the timestamp field name must not include an @.")
168
+ raise e
169
+ rescue => e
170
+ @logger.debug("Couldn't generate protobuf: #{e.inspect}")
171
+ raise e
151
172
  end
152
173
 
153
174
 
154
175
  def pb3_encode(datahash, class_name)
155
- next unless datahash.is_a?(::Hash)
156
-
157
- # Preparation: the data cannot be encoded until certain criteria are met:
158
- # 1) remove @ signs from keys.
159
- # 2) convert timestamps and other objects to strings
160
- datahash = datahash.inject({}){|x,(k,v)| x[k.gsub(/@/,'').to_sym] = (should_convert_to_string?(v) ? v.to_s : v); x}
176
+ if datahash.is_a?(::Hash)
177
+
161
178
 
162
- # Check if any of the fields in this hash are protobuf classes and if so, create a builder for them.
163
- meta = @metainfo_messageclasses[class_name]
164
- if meta
165
- meta.map do | (field_name,class_name) |
166
- key = field_name.to_sym
167
- if datahash.include?(key)
168
- original_value = datahash[key]
169
- datahash[key] =
170
- if original_value.is_a?(::Array)
171
- # make this field an array/list of protobuf objects
172
- # value is a list of hashed complex objects, each of which needs to be protobuffed and
173
- # put back into the list.
179
+
180
+ # Preparation: the data cannot be encoded until certain criteria are met:
181
+ # 1) remove @ signs from keys.
182
+ # 2) convert timestamps and other objects to strings
183
+ datahash = datahash.inject({}){|x,(k,v)| x[k.gsub(/@/,'').to_sym] = (should_convert_to_string?(v) ? v.to_s : v); x}
184
+
185
+ # Check if any of the fields in this hash are protobuf classes and if so, create a builder for them.
186
+ meta = @metainfo_messageclasses[class_name]
187
+ if meta
188
+ meta.map do | (field_name,class_name) |
189
+ key = field_name.to_sym
190
+ if datahash.include?(key)
191
+ original_value = datahash[key]
192
+ datahash[key] =
193
+ if original_value.is_a?(::Array)
194
+ # make this field an array/list of protobuf objects
195
+ # value is a list of hashed complex objects, each of which needs to be protobuffed and
196
+ # put back into the list.
197
+ original_value.map { |x| pb3_encode(x, class_name) }
198
+ original_value
199
+ else
200
+ r = pb3_encode(original_value, class_name)
201
+ builder = Google::Protobuf::DescriptorPool.generated_pool.lookup(class_name).msgclass
202
+ builder.new(r)
203
+ end # if is array
204
+ end # if datahash_include
205
+ end # do
206
+ end # if meta
207
+
208
+ # Check if any of the fields in this hash are enum classes and if so, create a builder for them.
209
+ meta = @metainfo_enumclasses[class_name]
210
+ if meta
211
+ meta.map do | (field_name,class_name) |
212
+ key = field_name.to_sym
213
+ if datahash.include?(key)
214
+ original_value = datahash[key]
215
+ datahash[key] = case original_value
216
+ when ::Array
174
217
  original_value.map { |x| pb3_encode(x, class_name) }
175
218
  original_value
176
- else
177
- r = pb3_encode(original_value, class_name)
178
- builder = Google::Protobuf::DescriptorPool.generated_pool.lookup(class_name).msgclass
179
- builder.new(r)
180
- end # if is array
181
- end # if datahash_include
182
- end # do
183
- end # if meta
184
- # Check if any of the fields in this hash are enum classes and if so, create a builder for them.
185
- meta = @metainfo_enumclasses[class_name]
186
- if meta
187
- meta.map do | (field_name,class_name) |
188
- key = field_name.to_sym
189
- if datahash.include?(key)
190
- original_value = datahash[key]
191
- datahash[key] =
192
- if original_value.is_a?(::Array)
193
- original_value.map { |x| pb3_encode(x, class_name) }
194
- original_value
195
- else
196
- if original_value.is_a?(Fixnum)
219
+ when Fixnum
197
220
  original_value # integers will be automatically converted into enum
198
- else
221
+ # else
199
222
  # feature request: support for providing integers as strings or symbols.
200
223
  # not fully tested yet:
201
224
  # begin
@@ -204,62 +227,59 @@ class LogStash::Codecs::Protobuf < LogStash::Codecs::Base
204
227
  # mod.const_get(class_name)
205
228
  # end # do
206
229
  # rescue => e
207
- # @logger.debug("Encoding error 3: could not translate #{original_value} into enum. ${e}")
230
+ # @logger.debug("Encoding error 3: could not translate #{original_value} into enum. #{e}")
208
231
  # raise e
209
232
  # end
210
- end # if is a fixnum
211
- end # if is array
212
- end # if datahash_include
213
- end # do
214
- end # if meta
233
+ end
234
+ end # if datahash_include
235
+ end # do
236
+ end # if meta
237
+ end
215
238
  datahash
216
239
  end
217
240
 
218
241
  def pb2_encode_wrapper(event)
219
- begin
220
- data = pb2_encode(event.to_hash, @class_name)
221
- msg = @pb_builder.new(data)
222
- msg.serialize_to_string
223
- rescue NoMethodError => e
224
- @logger.debug("Encoding error 2. Probably mismatching protobuf definition. Required fields in the protobuf definition are: " + event.to_hash.keys.join(", ") + " and the timestamp field name must not include a @. ")
225
- raise e
226
- rescue => e
227
- @logger.debug("Encoding error 1: ${e}")
228
- raise e
229
- end
242
+ data = pb2_encode(event.to_hash, @class_name)
243
+ msg = @pb_builder.new(data)
244
+ msg.serialize_to_string
245
+ rescue NoMethodError => e
246
+ @logger.debug("Encoding error 2. Probably mismatching protobuf definition. Required fields in the protobuf definition are: " + event.to_hash.keys.join(", ") + " and the timestamp field name must not include a @. ")
247
+ raise e
248
+ rescue => e
249
+ @logger.debug("Encoding error 1: #{e.inspect}")
250
+ raise e
230
251
  end
231
252
 
232
253
 
233
- def pb2_encode(datahash, class_name)
234
- next unless datahash.is_a?(::Hash)
235
-
236
- # Preparation: the data cannot be encoded until certain criteria are met:
237
- # 1) remove @ signs from keys.
238
- # 2) convert timestamps and other objects to strings
239
- datahash = ::Hash[datahash.map{|(k,v)| [k.to_s.dup.gsub(/@/,''), (should_convert_to_string?(v) ? v.to_s : v)] }]
240
-
241
- # Check if any of the fields in this hash are protobuf classes and if so, create a builder for them.
242
- meta = @metainfo_messageclasses[class_name]
243
- if meta
244
- meta.map do | (k,class_name) |
245
- if datahash.include?(k)
246
- original_value = datahash[k]
247
- p
248
- datahash[k] =
249
- if original_value.is_a?(::Array)
250
- # make this field an array/list of protobuf objects
251
- # value is a list of hashed complex objects, each of which needs to be protobuffed and
252
- # put back into the list.
253
- original_value.map { |x| pb2_encode(x, class_name) }
254
- original_value
255
- else
256
- proto_obj = pb2_create_instance(class_name)
257
- proto_obj.new(pb2_encode(original_value, class_name))
258
- end # if is array
259
- end # if datahash_include
260
- end # do
261
- end # if meta
262
254
 
255
+ def pb2_encode(datahash, class_name)
256
+ if datahash.is_a?(::Hash)
257
+ # Preparation: the data cannot be encoded until certain criteria are met:
258
+ # 1) remove @ signs from keys.
259
+ # 2) convert timestamps and other objects to strings
260
+ datahash = ::Hash[datahash.map{|(k,v)| [k.to_s.dup.gsub(/@/,''), (should_convert_to_string?(v) ? v.to_s : v)] }]
261
+
262
+ # Check if any of the fields in this hash are protobuf classes and if so, create a builder for them.
263
+ meta = @metainfo_messageclasses[class_name]
264
+ if meta
265
+ meta.map do | (k,c) |
266
+ if datahash.include?(k)
267
+ original_value = datahash[k]
268
+ datahash[k] =
269
+ if original_value.is_a?(::Array)
270
+ # make this field an array/list of protobuf objects
271
+ # value is a list of hashed complex objects, each of which needs to be protobuffed and
272
+ # put back into the list.
273
+ original_value.map { |x| pb2_encode(x, c) }
274
+ original_value
275
+ else
276
+ proto_obj = pb2_create_instance(c)
277
+ proto_obj.new(pb2_encode(original_value, c)) # this line is reached in the colourtest for an enum. Enums should not be instantiated. Should enums even be in the messageclasses? I dont think so! TODO bug
278
+ end # if is array
279
+ end # if datahash_include
280
+ end # do
281
+ end # if meta
282
+ end
263
283
  datahash
264
284
  end
265
285
 
@@ -270,104 +290,121 @@ class LogStash::Codecs::Protobuf < LogStash::Codecs::Base
270
290
 
271
291
 
272
292
  def pb2_create_instance(name)
273
- begin
274
- @logger.debug("Creating instance of " + name)
275
- name.split('::').inject(Object) { |n,c| n.const_get c }
276
- end
293
+ @logger.debug("Creating instance of " + name)
294
+ name.split('::').inject(Object) { |n,c| n.const_get c }
277
295
  end
278
296
 
279
297
 
280
298
  def pb3_metadata_analyis(filename)
281
299
  regex_class_name = /\s*add_message "(?<name>.+?)" do\s+/ # TODO optimize both regexes for speed (negative lookahead)
282
300
  regex_pbdefs = /\s*(optional|repeated)(\s*):(?<name>.+),(\s*):(?<type>\w+),(\s*)(?<position>\d+)(, \"(?<enum_class>.*?)\")?/
283
- # Example
284
- # optional :father, :message, 10, "Unicorn"
285
- # repeated :favourite_numbers, :int32, 5
286
- begin
287
- class_name = ""
288
- type = ""
289
- field_name = ""
290
- File.readlines(filename).each do |line|
291
- if ! (line =~ regex_class_name).nil?
292
- class_name = $1
293
- @metainfo_messageclasses[class_name] = {}
294
- @metainfo_enumclasses[class_name] = {}
295
- end
296
- if ! (line =~ regex_pbdefs).nil?
297
- field_name = $1
298
- type = $2
299
- field_class_name = $4
300
- if type == "message"
301
- @metainfo_messageclasses[class_name][field_name] = field_class_name
302
- elsif type == "enum"
303
- @metainfo_enumclasses[class_name][field_name] = field_class_name
304
- end
301
+ class_name = ""
302
+ type = ""
303
+ field_name = ""
304
+ File.readlines(filename).each do |line|
305
+ if ! (line =~ regex_class_name).nil?
306
+ class_name = $1
307
+ @metainfo_messageclasses[class_name] = {}
308
+ @metainfo_enumclasses[class_name] = {}
309
+ end # if
310
+ if ! (line =~ regex_pbdefs).nil?
311
+ field_name = $1
312
+ type = $2
313
+ field_class_name = $4
314
+ if type == "message"
315
+ @metainfo_messageclasses[class_name][field_name] = field_class_name
316
+ elsif type == "enum"
317
+ @metainfo_enumclasses[class_name][field_name] = field_class_name
305
318
  end
306
- end
307
- rescue Exception => e
308
- @logger.warn("Error 3: unable to read pb definition from file " + filename+ ". Reason: #{e.inspect}. Last settings were: class #{class_name} field #{field_name} type #{type}. Backtrace: " + e.backtrace.inspect.to_s)
309
- raise e
310
- end
319
+ end # if
320
+ end # readlines
311
321
  if class_name.nil?
312
322
  @logger.warn("Error 4: class name not found in file " + filename)
313
323
  raise ArgumentError, "Invalid protobuf file: " + filename
314
- end
324
+ end
325
+ rescue Exception => e
326
+ @logger.warn("Error 3: unable to read pb definition from file " + filename+ ". Reason: #{e.inspect}. Last settings were: class #{class_name} field #{field_name} type #{type}. Backtrace: " + e.backtrace.inspect.to_s)
327
+ raise e
315
328
  end
329
+
330
+
316
331
 
317
332
  def pb2_metadata_analyis(filename)
318
- regex_class_name = /\s*class\s*(?<name>.+?)\s+/
319
- regex_module_name = /\s*module\s*(?<name>.+?)\s+/
333
+ regex_class_start = /\s*set_fully_qualified_name \"(?<name>.+)\".*?/
334
+ regex_enum_name = /\s*include ..ProtocolBuffers..Enum\s*/
320
335
  regex_pbdefs = /\s*(optional|repeated)(\s*):(?<type>.+),(\s*):(?<name>\w+),(\s*)(?<position>\d+)/
321
336
  # now we also need to find out which class it contains and the protobuf definitions in it.
322
337
  # We'll unfortunately need that later so that we can create nested objects.
323
- begin
324
- class_name = ""
325
- type = ""
326
- field_name = ""
327
- classname_found = false
328
- File.readlines(filename).each do |line|
329
- if ! (line =~ regex_module_name).nil? && !classname_found # because it might be declared twice in the file
330
- class_name << $1
331
- class_name << "::"
332
-
333
- end
334
- if ! (line =~ regex_class_name).nil? && !classname_found # because it might be declared twice in the file
335
- class_name << $1
336
- @metainfo_messageclasses[class_name] = {}
337
- classname_found = true
338
+
339
+ class_name = ""
340
+ type = ""
341
+ field_name = ""
342
+ is_enum_class = false
343
+
344
+ File.readlines(filename).each do |line|
345
+ if ! (line =~ regex_enum_name).nil?
346
+ is_enum_class= true
347
+ end
348
+
349
+ if ! (line =~ regex_class_start).nil?
350
+ class_name = $1.gsub('.',"::").split('::').map {|word| word.capitalize}.join('::')
351
+ if is_enum_class
352
+ @metainfo_pb2_enumlist << class_name.downcase
338
353
  end
339
- if ! (line =~ regex_pbdefs).nil?
340
- type = $1
341
- field_name = $2
342
- if type =~ /::/
343
- @metainfo_messageclasses[class_name][field_name] = type.gsub!(/^:/,"")
344
-
354
+ is_enum_class= false # reset when next class starts
355
+ end
356
+ if ! (line =~ regex_pbdefs).nil?
357
+ type = $1
358
+ field_name = $2
359
+ if type =~ /::/
360
+ clean_type = type.gsub(/^:/,"")
361
+ e = @metainfo_pb2_enumlist.include? clean_type.downcase
362
+
363
+ if e
364
+ if not @metainfo_enumclasses.key? class_name
365
+ @metainfo_enumclasses[class_name] = {}
366
+ end
367
+ @metainfo_enumclasses[class_name][field_name] = clean_type
368
+ else
369
+ if not @metainfo_messageclasses.key? class_name
370
+ @metainfo_messageclasses[class_name] = {}
371
+ end
372
+ @metainfo_messageclasses[class_name][field_name] = clean_type
345
373
  end
346
374
  end
347
375
  end
348
- rescue Exception => e
349
- @logger.warn("Error 3: unable to read pb definition from file " + filename+ ". Reason: #{e.inspect}. Last settings were: class #{class_name} field #{field_name} type #{type}. Backtrace: " + e.backtrace.inspect.to_s)
350
- raise e
351
376
  end
352
377
  if class_name.nil?
353
378
  @logger.warn("Error 4: class name not found in file " + filename)
354
379
  raise ArgumentError, "Invalid protobuf file: " + filename
355
- end
380
+ end
381
+ rescue LoadError => e
382
+ raise ArgumentError.new("Could not load file: " + filename + ". Please try to use absolute pathes. Current working dir: " + Dir.pwd + ", loadpath: " + $LOAD_PATH.join(" "))
383
+ rescue => e
384
+
385
+ @logger.warn("Error 3: unable to read pb definition from file " + filename+ ". Reason: #{e.inspect}. Last settings were: class #{class_name} field #{field_name} type #{type}. Backtrace: " + e.backtrace.inspect.to_s)
386
+ raise e
356
387
  end
388
+
357
389
 
358
390
  def load_protobuf_definition(filename)
359
- begin
360
- if filename.end_with? ('.rb')
361
- @logger.debug("Including protobuf file: " + filename)
391
+ if filename.end_with? ('.rb')
392
+ if (Pathname.new filename).absolute?
362
393
  require filename
363
- if @protobuf_version_3
364
- pb3_metadata_analyis(filename)
365
- else
366
- pb2_metadata_analyis(filename)
367
- end
368
- else
369
- @logger.warn("Not a ruby file: " + filename)
394
+ else
395
+ require_relative filename # needed for the test cases
396
+ r = File.expand_path(File.dirname(__FILE__))
397
+ filename = File.join(r, filename) # make the path absolute
398
+ end
399
+
400
+ if @protobuf_version == 3
401
+ pb3_metadata_analyis(filename)
402
+ else
403
+ pb2_metadata_analyis(filename)
370
404
  end
405
+
406
+ else
407
+ @logger.warn("Not a ruby file: " + filename)
371
408
  end
372
409
  end
373
410