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