lib/semantic_logger/appender/syslog.rb in semantic_logger-2.21.0 vs lib/semantic_logger/appender/syslog.rb in semantic_logger-3.0.0

- old
+ new

@@ -1,43 +1,38 @@ -# syslog appender for SemanticLogger - Supports local and remote syslog (over TCP or UDP) +require 'syslog' +require 'uri' +require 'socket' + +# Send log messages to local syslog, or remote syslog servers over TCP or UDP. # -# Example 1 -# Log to the local syslog. +# Example: Log to a local Syslog daemon +# SemanticLogger.add_appender(SemanticLogger::Appender::Syslog.new) # -# require 'semantic_logger' -# SemanticLogger.default_level = :trace +# Example: Log to a remote Syslog server using TCP: +# appender = SemanticLogger::Appender::Syslog.new( +# url: 'tcp://myloghost:514' +# ) # -# syslog_appender = SemanticLogger::Appender::Syslog.new -# SemanticLogger.add_appender(syslog_appender) +# # Optional: Add filter to exclude health_check, or other log entries +# appender.filter = Proc.new { |log| log.message !~ /(health_check|Not logged in)/ } # -# logger = SemanticLogger['SyslogAppenderExample'] -# logger.info "Info Hello! - This message should appear in the local syslog!" +# SemanticLogger.add_appender(appender) # +# Example: Log to a remote Syslog server using UDP: +# appender = SemanticLogger::Appender::Syslog.new( +# url: 'udp://myloghost:514' +# ) # -# Example 2 -# Send to a remote syslog appender - myloghost - over TCP on port 514. -# Tested with syslog-ng as part of an ELSA installation. -# https://code.google.com/p/enterprise-log-search-and-archive/ +# # Optional: Add filter to exclude health_check, or other log entries +# appender.filter = Proc.new { |log| log.message !~ /(health_check|Not logged in)/ } # -# require 'semantic_logger' -# # Only log warn and above messages to the remote syslog. -# syslog_appender = SemanticLogger::Appender::Syslog.new(level: :warn, server: 'tcp://myloghost:514') -# SemanticLogger.add_appender(syslog_appender) -# -# logger = SemanticLogger['SyslogAppenderExample'] -# logger.info "Info Hello! - The log level is too low and will not be logged." -# logger.error "Error! Error! - This message should appear in the remote syslog!" -# -require 'syslog' -require 'uri' -require 'socket' - +# SemanticLogger.add_appender(appender) module SemanticLogger module Appender class Syslog < SemanticLogger::Appender::Base - attr_reader :remote_syslog, :server, :host, :port, :protocol, :facility, :local_hostname + attr_reader :remote_syslog, :url, :server, :port, :protocol, :facility, :host, :application # Default mapping of ruby log levels to syslog log levels # # ::Syslog::LOG_EMERG - "System is unusable" # ::Syslog::LOG_ALERT - "Action needs to be taken immediately" @@ -54,28 +49,61 @@ info: ::Syslog::LOG_NOTICE, debug: ::Syslog::LOG_INFO, trace: ::Syslog::LOG_DEBUG } - # For more information on the Syslog constants used below see http://ruby-doc.org/stdlib-2.0.0/libdoc/syslog/rdoc/Syslog.html + # Create a Syslog appender instance. + # # Parameters + # url: [String] + # Default: 'syslog://localhost' + # For writing logs to a remote syslog server + # URL of server: protocol://host:port + # Uses port 514 by default for TCP and UDP. + # local syslog example: 'syslog://localhost' + # TCP example with default port: 'tcp://logger' + # TCP example with custom port: 'tcp://logger:8514' + # UDP example with default port: 'udp://logger' + # UDP example with custom port: 'udp://logger:8514' + # When using the :syslog protocol, logs will always be sent to the localhost syslog # - # :ident [String] - # Identity of the program - # Default: 'ruby' + # host: [String] + # Host name to provide to the remote syslog. + # Default: SemanticLogger.host # - # :options [Integer] + # tcp_client: [Hash] + # Default: {} + # Only used with the TCP protocol. + # Specify custom parameters to pass into Net::TCPClient.new + # For a list of options see the net_tcp_client documentation: + # https://www.omniref.com/ruby/gems/net_tcp_client/1.0.0/symbols/Net::TCPClient/initialize + # + # level: [:trace | :debug | :info | :warn | :error | :fatal] + # Override the log level for this appender. + # Default: SemanticLogger.default_level + # + # filter: [Regexp|Proc] + # RegExp: Only include log messages where the class name matches the supplied. + # regular expression. All other messages will be ignored. + # Proc: Only include log messages where the supplied Proc returns true + # The Proc must return true or false. + # + # application: [String] + # Identity of the program. + # Default: SemanticLogger.application + # + # options: [Integer] # Default: ::Syslog::LOG_PID | ::Syslog::LOG_CONS # Any of the following (options can be logically OR'd together) # ::Syslog::LOG_CONS # ::Syslog::LOG_NDELAY # ::Syslog::LOG_NOWAIT # ::Syslog::LOG_ODELAY # ::Syslog::LOG_PERROR # ::Syslog::LOG_PID # - # :facility [Integer] + # facility: [Integer] # Default: ::Syslog::LOG_USER # Type of program (can be logically OR'd together) # ::Syslog::LOG_AUTH # ::Syslog::LOG_AUTHPRIV # ::Syslog::LOG_CONSOLE @@ -98,15 +126,11 @@ # ::Syslog::LOG_LOCAL4 # ::Syslog::LOG_LOCAL5 # ::Syslog::LOG_LOCAL6 # ::Syslog::LOG_LOCAL7 # - # :level [Symbol] - # Default: SemanticLogger's log level. - # The minimum level at which this appender will write logs. Any log messages below this level will be ignored. - # - # :level_map [Hash] + # level_map: [Hash] # Supply a custom map of SemanticLogger levels to syslog levels. # For example, passing in { warn: ::Syslog::LOG_NOTICE } # would result in a log mapping that matches the default level map, # except for :warn, which ends up with a LOG_NOTICE level instead of a # LOG_WARNING one. @@ -117,56 +141,33 @@ # warn: ::Syslog::LOG_WARNING, # info: ::Syslog::LOG_NOTICE, # debug: ::Syslog::LOG_INFO, # trace: ::Syslog::LOG_DEBUG # } - # - # :local_hostname [String] - # Default: Socket.gethostname || `hostname`.strip - # Hostname to provide to the remote syslog. - # - # :server [String] - # Default: 'syslog://localhost' - # For writing logs to a remote syslog server - # URI of server: protocol://host:port - # Uses port 514 by default for TCP and UDP. - # local syslog example: 'syslog://localhost' - # TCP example with default port: 'tcp://logger' - # TCP example with custom port: 'tcp://logger:8514' - # UDP example with default port: 'udp://logger' - # UDP example with custom port: 'udp://logger:8514' - # When using the :syslog protocol, logs will always be sent to the localhost syslog - # - # :tcp_client [Hash] - # Default: {} - # Only used with the TCP protocol. - # Specify custom parameters to pass into Net::TCPClient.new - # For a list of options see the net_tcp_client documentation: - # https://www.omniref.com/ruby/gems/net_tcp_client/1.0.0/symbols/Net::TCPClient/initialize - def initialize(params = {}, &block) - params = params.dup - @ident = params.delete(:ident) || 'ruby' - @options = params.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS) - @facility = params.delete(:facility) || ::Syslog::LOG_USER - filter = params.delete(:filter) - level = params.delete(:level) - level_map = params.delete(:level_map) + def initialize(options = {}, &block) + options = options.dup + level = options.delete(:level) + filter = options.delete(:filter) + @application = options.delete(:application) || options.delete(:ident) || 'ruby' + @options = options.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS) + @facility = options.delete(:facility) || ::Syslog::LOG_USER + level_map = options.delete(:level_map) + @url = options.delete(:url) || options.delete(:server) || 'syslog://localhost' + uri = URI(@url) + @server = uri.host || 'localhost' + @protocol = (uri.scheme || :syslog).to_sym + @port = uri.port || 514 + @server = 'localhost' if @protocol == :syslog + @host = options.delete(:host) || options.delete(:local_hostname) || SemanticLogger.host + @tcp_client_options = options.delete(:tcp_client) + + raise "Unknown protocol #{@protocol}!" unless [:syslog, :tcp, :udp].include?(@protocol) + raise(ArgumentError, "Unknown options: #{options.inspect}") if options.size > 0 + @level_map = DEFAULT_LEVEL_MAP.dup @level_map.update(level_map) if level_map - @server = params.delete(:server) || 'syslog://localhost' - uri = URI(@server) - @host = uri.host || 'localhost' - @protocol = (uri.scheme || :syslog).to_sym - raise "Unknown protocol #{@protocol}!" unless [:syslog, :tcp, :udp].include?(@protocol) - @host = 'localhost' if @protocol == :syslog - @port = URI(@server).port || 514 - @local_hostname = params.delete(:local_hostname) || Socket.gethostname || `hostname`.strip - @tcp_client_options = params.delete(:tcp_client) - # Warn about any unknown configuration options. - params.each_pair { |key, val| SemanticLogger::Logger.logger.warn "Ignoring unknown configuration option: #{key.inspect} => #{val.inspect}" } - # The syslog_protocol gem is required when logging over TCP or UDP. if [:tcp, :udp].include?(@protocol) begin require 'syslog_protocol' rescue LoadError @@ -174,11 +175,11 @@ end # The net_tcp_client gem is required when logging over TCP. if protocol == :tcp @tcp_client_options ||= {} - @tcp_client_options[:server] = "#{@host}:#{@port}" + @tcp_client_options[:server] = "#{@server}:#{@port}" begin require 'net/tcp_client' rescue LoadError raise 'Missing gem: net_tcp_client. This gem is required when logging over TCP. To fix this error: gem install net_tcp_client' end @@ -193,11 +194,11 @@ # After forking an active process call #reopen to re-open # open the handles to resources def reopen case @protocol when :syslog - ::Syslog.open(@ident, @options, @facility) + ::Syslog.open(@application, @options, @facility) when :tcp # Use the local logger for @remote_syslog so errors with the remote logger can be recorded locally. @tcp_client_options[:logger] = SemanticLogger::Logger.logger @remote_syslog = Net::TCPClient.new(@tcp_client_options) when :udp @@ -205,24 +206,24 @@ else raise "Unsupported protocol: #{@protocol}" end end - # Write the log using the specified protocol and host. + # Write the log using the specified protocol and server. def log(log) # Ensure minimum log level is met, and check filter return false if (level_index > (log.level_index || 0)) || !include_message?(log) case @protocol when :syslog # Since the Ruby Syslog API supports sprintf format strings, double up all existing '%' - message = formatter.call(log).gsub '%', '%%' + message = formatter.call(log, self).gsub '%', '%%' ::Syslog.log @level_map[log.level], message when :tcp @remote_syslog.retry_on_connection_failure { @remote_syslog.write("#{syslog_packet_formatter(log)}\r\n") } when :udp - @remote_syslog.send syslog_packet_formatter(log), 0, @host, @port + @remote_syslog.send syslog_packet_formatter(log), 0, @server, @port else raise "Unsupported protocol: #{protocol}" end true end @@ -230,39 +231,51 @@ # Flush is called by the semantic_logger during shutdown. def flush @remote_syslog.flush if @remote_syslog && @remote_syslog.respond_to?(:flush) end - # Custom log formatter for syslog + # Custom log formatter for syslog. + # Only difference is the removal of the timestamp string since it is in the syslog packet. def default_formatter Proc.new do |log| - tags = log.tags.collect { |tag| "[#{tag}]" }.join(" ") + " " if log.tags && (log.tags.size > 0) + # Header with date, time, log level and process info + entry = "#{log.level_to_s} [#{log.process_info}]" - message = log.message.to_s - message << ' -- ' << log.payload.inspect if log.payload - log.each_exception do |exception, i| - if i == 0 - message << ' -- ' - else - message << "\nCause: " - end - message << "#{exception.class}: #{exception.message}\n#{(exception.backtrace || []).join("\n")}" - end + # Tags + entry << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0) - duration_str = log.duration ? "(#{'%.1f' % log.duration}ms) " : '' + # Duration + entry << " (#{log.duration_human})" if log.duration - "#{log.level.to_s[0..0].upcase} [#{$$}:#{log.thread_name}] #{tags}#{duration_str}#{log.name} -- #{message}" + # Class / app name + entry << " #{log.name}" + + # Log message + entry << " -- #{log.message}" if log.message + + # Payload + if payload = log.payload_to_s(false) + entry << ' -- ' << payload + end + + # Exceptions + if log.exception + entry << " -- Exception: #{log.exception.class}: #{log.exception.message}\n" + entry << log.backtrace_to_s + end + entry end end # Format the syslog packet so it can be sent over TCP or UDP def syslog_packet_formatter(log) packet = SyslogProtocol::Packet.new - packet.hostname = @local_hostname + packet.hostname = @host packet.facility = @facility packet.severity = @level_map[log.level] - packet.tag = @ident - packet.content = default_formatter.call(log) + packet.tag = @application + packet.content = default_formatter.call(log, self) + packet.time = log.time packet.to_s end end end end