/lib/logstash/filters/multiline.rb

https://github.com/rweald/logstash · Ruby · 174 lines · 85 code · 17 blank · 72 comment · 12 complexity · cc08a8a3dbfc057fa1431aa6437e4a68 MD5 · raw file

  1. # multiline filter
  2. #
  3. # This filter will collapse multiline messages into a single event.
  4. #
  5. require "logstash/filters/base"
  6. require "logstash/namespace"
  7. # The multiline filter is for combining multiple events from a single source
  8. # into the same event.
  9. #
  10. # The original goal of this filter was to allow joining of multi-line messages
  11. # from files into a single event. For example - joining java exception and
  12. # stacktrace messages into a single event.
  13. #
  14. # TODO(sissel): Document any issues?
  15. # The config looks like this:
  16. #
  17. # filter {
  18. # multiline {
  19. # type => "type"
  20. # pattern => "pattern, a regexp"
  21. # negate => boolean
  22. # what => "previous" or "next"
  23. # }
  24. # }
  25. #
  26. # The 'regexp' should match what you believe to be an indicator that
  27. # the field is part of a multi-line event
  28. #
  29. # The 'what' must be "previous" or "next" and indicates the relation
  30. # to the multi-line event.
  31. #
  32. # The 'negate' can be "true" or "false" (defaults false). If true, a
  33. # message not matching the pattern will constitute a match of the multiline
  34. # filter and the what will be applied. (vice-versa is also true)
  35. #
  36. # For example, java stack traces are multiline and usually have the message
  37. # starting at the far-left, then each subsequent line indented. Do this:
  38. #
  39. # filter {
  40. # multiline {
  41. # type => "somefiletype"
  42. # pattern => "^\\s"
  43. # what => "previous"
  44. # }
  45. # }
  46. #
  47. # This says that any line starting with whitespace belongs to the previous line.
  48. #
  49. # Another example is C line continuations (backslash). Here's how to do that:
  50. #
  51. # filter {
  52. # multiline {
  53. # type => "somefiletype "
  54. # pattern => "\\$"
  55. # what => "next"
  56. # }
  57. # }
  58. #
  59. class LogStash::Filters::Multiline < LogStash::Filters::Base
  60. config_name "multiline"
  61. # The regular expression to match
  62. config :pattern, :validate => :string, :require => true
  63. # If the pattern matched, does event belong to the next or previous event?
  64. config :what, :validate => ["previous", "next"], :require => true
  65. # Negate the regexp pattern ('if not matched')
  66. config :negate, :validate => :boolean, :default => false
  67. public
  68. def initialize(config = {})
  69. super
  70. # This filter needs to keep state.
  71. @types = Hash.new { |h,k| h[k] = [] }
  72. @pending = Hash.new
  73. end # def initialize
  74. public
  75. def register
  76. @logger.debug "Setting type #{@type.inspect} to the config #{@config.inspect}"
  77. begin
  78. @pattern = Regexp.new(@pattern)
  79. rescue RegexpError => e
  80. @logger.fatal(["Invalid pattern for multiline filter on type '#{@type}'",
  81. @pattern, e])
  82. end
  83. end # def register
  84. public
  85. def filter(event)
  86. return unless event.type == @type
  87. match = @pattern.match(event.message)
  88. key = [event.source, event.type]
  89. pending = @pending[key]
  90. @logger.debug(["Reg: ", @pattern, event.message, { :match => match, :negate => @negate }])
  91. # Add negate option
  92. match = (match and !@negate) || (!match and @negate)
  93. case @what
  94. when "previous"
  95. if match
  96. event.tags |= ["multiline"]
  97. # previous previous line is part of this event.
  98. # append it to the event and cancel it
  99. if pending
  100. pending.append(event)
  101. else
  102. @pending[key] = event
  103. end
  104. event.cancel
  105. else
  106. # this line is not part of the previous event
  107. # if we have a pending event, it's done, send it.
  108. # put the current event into pending
  109. if pending
  110. tmp = event.to_hash
  111. event.overwrite(pending)
  112. @pending[key] = LogStash::Event.new(tmp)
  113. else
  114. @pending[key] = event
  115. event.cancel
  116. end # if/else pending
  117. end # if/else match
  118. when "next"
  119. if match
  120. event.tags |= ["multiline"]
  121. # this line is part of a multiline event, the next
  122. # line will be part, too, put it into pending.
  123. if pending
  124. pending.append(event)
  125. else
  126. @pending[key] = event
  127. end
  128. event.cancel
  129. else
  130. # if we have something in pending, join it with this message
  131. # and send it. otherwise, this is a new message and not part of
  132. # multiline, send it.
  133. if pending
  134. pending.append(event)
  135. event.overwrite(pending.to_hash)
  136. @pending.delete(key)
  137. end
  138. end # if/else match
  139. else
  140. @logger.warn(["Unknown multiline 'what' value.", { :what => @what }])
  141. end # case @what
  142. if !event.cancelled?
  143. filter_matched(event)
  144. end
  145. filter_matched(event) if !event.cancelled?
  146. end # def filter
  147. # flush any pending messages
  148. public
  149. def flush(source, type)
  150. key = [source, type]
  151. if @pending[key]
  152. event = @pending[key]
  153. @pending.delete(key)
  154. end
  155. return event
  156. end # def flush
  157. end # class LogStash::Filters::Date