PageRenderTime 26ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/logstash/inputs/syslog.rb

https://github.com/jnewland/logstash
Ruby | 194 lines | 137 code | 23 blank | 34 comment | 10 complexity | 3e3dfdc251782e8fca5d46cd509c875e MD5 | raw file
  1. require "date"
  2. require "logstash/inputs/base"
  3. require "logstash/namespace"
  4. require "logstash/time" # should really use the filters/date.rb bits
  5. require "socket"
  6. # Read syslog messages as events over the network.
  7. #
  8. # This input is a good choice if you already use syslog today.
  9. # It is also a good choice if you want to receive logs from
  10. # appliances and network devices where you cannot run your own
  11. # log collector.
  12. #
  13. # Note: this input will start listeners on both TCP and UDP
  14. class LogStash::Inputs::Syslog < LogStash::Inputs::Base
  15. config_name "syslog"
  16. # The address to listen on
  17. config :host, :validate => :string, :default => "0.0.0.0"
  18. # The port to listen on. Remember that ports less than 1024 (privileged
  19. # ports) may require root to use.
  20. config :port, :validate => :number, :default => 514
  21. public
  22. def initialize(params)
  23. super
  24. BasicSocket.do_not_reverse_lookup = true
  25. # force "plain" format. others don't make sense here.
  26. @format = ["plain"]
  27. end # def initialize
  28. public
  29. def register
  30. # This comes from RFC3164, mostly.
  31. # Optional fields (priority, host, timestamp) are because some syslog implementations
  32. # don't send these under some circumstances.
  33. @@syslog_re ||= \
  34. /(?:<([0-9]{1,3})>)?(?:([A-z]{3} ?[0-9]{1,2} [0-9]{2}:[0-9]{2}:[0-9]{2}) )?(?:(\S*[^ :]) )?(.*)/
  35. # <priority> timestamp Mmm dd hh:mm:ss host msg
  36. @tcp_clients = []
  37. end # def register
  38. public
  39. def run(output_queue)
  40. # udp server
  41. Thread.new do
  42. LogStash::Util::set_thread_name("input|syslog|udp")
  43. begin
  44. udp_listener(output_queue)
  45. rescue => e
  46. @logger.warn("syslog udp listener died: #{$!}")
  47. @logger.debug(["Backtrace", e.backtrace])
  48. sleep(5)
  49. retry
  50. end # begin
  51. end # Thread.new
  52. # tcp server
  53. Thread.new do
  54. LogStash::Util::set_thread_name("input|syslog|tcp")
  55. begin
  56. tcp_listener(output_queue)
  57. rescue => e
  58. @logger.warn("syslog tcp listener died: #{$!}")
  59. @logger.debug(["Backtrace", e.backtrace])
  60. sleep(5)
  61. retry
  62. end # begin
  63. end # Thread.new
  64. end # def run
  65. private
  66. def udp_listener(output_queue)
  67. @logger.info("Starting syslog udp listener on #{@host}:#{@port}")
  68. if @udp
  69. @udp.close_read
  70. @udp.close_write
  71. end
  72. @udp = UDPSocket.new(Socket::AF_INET)
  73. @udp.bind(@host, @port)
  74. loop do
  75. line, client = @udp.recvfrom(9000)
  76. # Ruby uri sucks, so don't use it.
  77. source = "syslog://#{client[3]}/"
  78. e = to_event(line.chomp, source)
  79. if e
  80. syslog_relay(e, source)
  81. output_queue << e
  82. end
  83. end
  84. ensure
  85. if @udp
  86. @udp.close_read rescue nil
  87. @udp.close_write rescue nil
  88. end
  89. end # def udp_listener
  90. private
  91. def tcp_listener(output_queue)
  92. @logger.info("Starting syslog tcp listener on #{@host}:#{@port}")
  93. @tcp = TCPServer.new(@host, @port)
  94. @tcp_clients = []
  95. loop do
  96. client = @tcp.accept
  97. @tcp_clients << client
  98. Thread.new(client) do |client|
  99. ip, port = client.peeraddr[3], client.peeraddr[1]
  100. @logger.warn("got connection from #{ip}:#{port}")
  101. LogStash::Util::set_thread_name("input|syslog|tcp|#{ip}:#{port}}")
  102. if ip.include?(":") # ipv6
  103. source = "syslog://[#{ip}]/"
  104. else
  105. source = "syslog://#{ip}/"
  106. end
  107. begin
  108. client.each do |line|
  109. e = to_event(line.chomp, source)
  110. if e
  111. syslog_relay(e, source)
  112. output_queue << e
  113. end # e
  114. end # client.each
  115. rescue Errno::ECONNRESET
  116. end
  117. end # Thread.new
  118. end # loop do
  119. ensure
  120. # If we somehow have this left open, close it.
  121. @tcp_clients.each do |client|
  122. client.close rescue nil
  123. end
  124. @tcp.close if @tcp rescue nil
  125. end # def tcp_listener
  126. # Following RFC3164 where sane, we'll try to parse a received message
  127. # as if you were relaying a syslog message to it.
  128. # If the message cannot be recognized (see @@syslog_re), we'll
  129. # treat it like the whole event.message is correct and try to fill
  130. # the missing pieces (host, priority, etc)
  131. public
  132. def syslog_relay(event, url)
  133. match = @@syslog_re.match(event.message)
  134. if match
  135. # match[1,2,3,4] = {pri, timestamp, hostname, message}
  136. # Per RFC3164, priority = (facility * 8) + severity
  137. # = (facility << 3) & (severity)
  138. priority = match[1].to_i rescue 13
  139. severity = priority & 7 # 7 is 111 (3 bits)
  140. facility = priority >> 3
  141. event.fields["priority"] = priority
  142. event.fields["severity"] = severity
  143. event.fields["facility"] = facility
  144. host = match[3]
  145. # TODO(sissel): Use the date filter, somehow.
  146. if !match[2].nil?
  147. event.timestamp = LogStash::Time.to_iso8601(
  148. DateTime.strptime(match[2], "%b %d %H:%M:%S"))
  149. end
  150. # Hostname is optional, use if present in message, otherwise use source
  151. # address of message.
  152. if host
  153. event.source = "syslog://#{host}/"
  154. end
  155. event.message = match[4]
  156. else
  157. @logger.info(["NOT SYSLOG", event.message])
  158. url = "syslog://#{Socket.gethostname}/" if url == "syslog://127.0.0.1/"
  159. # RFC3164 says unknown messages get pri=13
  160. priority = 13
  161. severity = priority & 7 # 7 is 111 (3 bits)
  162. facility = priority >> 3
  163. event.fields["priority"] = 13
  164. event.fields["severity"] = 5 # 13 & 7 == 5
  165. event.fields["facility"] = 1 # 13 >> 3 == 1
  166. # Don't need to modify the message, here.
  167. # event.message = ...
  168. event.source = url
  169. end
  170. end # def syslog_relay
  171. end # class LogStash::Inputs::Syslog