lib/semantic_logger/appender/syslog.rb in semantic_logger-4.0.0 vs lib/semantic_logger/appender/syslog.rb in semantic_logger-4.1.0
- old
+ new
@@ -1,49 +1,40 @@
require 'syslog'
require 'uri'
require 'socket'
-
# Send log messages to local syslog, or remote syslog servers over TCP or UDP.
#
-# Example: Log to a local Syslog daemon
+# Example:
+# # Log to a local Syslog daemon
# SemanticLogger.add_appender(appender: :syslog)
#
-# Example: Log to a remote Syslog server using TCP:
+# Example:
+# # Log to a remote Syslog server over TCP:
# SemanticLogger.add_appender(
# appender: :syslog,
# url: 'tcp://myloghost:514'
# )
#
-# Example: Log to a remote Syslog server using UDP:
+# Example:
+# # Log to a remote Syslog server over UDP:
# SemanticLogger.add_appender(
# appender: :syslog,
# url: 'udp://myloghost:514'
# )
+#
+# Example:
+# # Log to a remote Syslog server using the CEE format over TCP:
+# SemanticLogger.add_appender(
+# appender: :syslog,
+# url: 'tcp://myloghost:514'
+# )
+#
module SemanticLogger
module Appender
class Syslog < SemanticLogger::Subscriber
+ attr_reader :remote_syslog, :url, :server, :port, :protocol, :facility, :options, :level_map
- # 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"
- # ::Syslog::LOG_CRIT - "A critical condition has occurred"
- # ::Syslog::LOG_ERR - "An error occurred"
- # ::Syslog::LOG_WARNING - "Warning of a possible problem"
- # ::Syslog::LOG_NOTICE - "A normal but significant condition occurred"
- # ::Syslog::LOG_INFO - "Informational message"
- # ::Syslog::LOG_DEBUG - "Debugging information"
- DEFAULT_LEVEL_MAP = {
- fatal: ::Syslog::LOG_CRIT,
- error: ::Syslog::LOG_ERR,
- warn: ::Syslog::LOG_WARNING,
- info: ::Syslog::LOG_NOTICE,
- debug: ::Syslog::LOG_INFO,
- trace: ::Syslog::LOG_DEBUG
- }
- attr_reader :remote_syslog, :url, :server, :port, :protocol, :facility
-
# Create a Syslog appender instance.
#
# Parameters
# url: [String]
# Default: 'syslog://localhost'
@@ -70,15 +61,10 @@
#
# level: [:trace | :debug | :info | :warn | :error | :fatal]
# Override the log level for this appender.
# Default: SemanticLogger.default_level
#
- # formatter: [Object|Proc]
- # An instance of a class that implements #call, or a Proc to be used to format
- # the output from this appender
- # Default: Use the built-in formatter (See: #call)
- #
# 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.
@@ -94,10 +80,13 @@
# ::Syslog::LOG_NDELAY
# ::Syslog::LOG_NOWAIT
# ::Syslog::LOG_ODELAY
# ::Syslog::LOG_PERROR
# ::Syslog::LOG_PID
+ # Note:
+ # - Only applicable when logging to a local syslog instance.
+ # I.e. When `url: 'syslog://localhost'`
#
# facility: [Integer]
# Default: ::Syslog::LOG_USER
# Type of program (can be logically OR'd together)
# ::Syslog::LOG_AUTH
@@ -122,83 +111,66 @@
# ::Syslog::LOG_LOCAL4
# ::Syslog::LOG_LOCAL5
# ::Syslog::LOG_LOCAL6
# ::Syslog::LOG_LOCAL7
#
- # level_map: [Hash]
+ # level_map: [Hash | SemanticLogger::Formatters::Syslog::LevelMap]
# 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.
- # Without overriding any parameters, the level map will be
- # LEVEL_MAP = {
- # fatal: ::Syslog::LOG_CRIT,
- # error: ::Syslog::LOG_ERR,
- # warn: ::Syslog::LOG_WARNING,
- # info: ::Syslog::LOG_NOTICE,
- # debug: ::Syslog::LOG_INFO,
- # trace: ::Syslog::LOG_DEBUG
- # }
#
- # format: [Symbol]
- # Format for the Syslog message
- # :syslog uses the default syslog format
- # :json uses the CEE JSON Syslog format
- # Example: "@cee: #{JSON.dump(data)}"
- # Default: :syslog
- def initialize(options = {}, &block)
- options = options.dup
- @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'
+ # Example:
+ # # Change the warn level to LOG_NOTICE level instead of a the default of LOG_WARNING.
+ # SemanticLogger.add_appender(appender: :syslog, level_map: {warn: ::Syslog::LOG_NOTICE})
+ def initialize(url: 'syslog://localhost',
+ facility: ::Syslog::LOG_USER, level_map: SemanticLogger::Formatters::Syslog::LevelMap.new, options: ::Syslog::LOG_PID|::Syslog::LOG_CONS,
+ tcp_client: {},
+ level: nil, formatter: nil, filter: nil, application: nil, host: nil, &block)
+
+ @options = options
+ @facility = facility
+ @level_map = level_map
+ @url = url
uri = URI(@url)
@server = uri.host || 'localhost'
@protocol = (uri.scheme || :syslog).to_sym
@port = uri.port || 514
@server = 'localhost' if @protocol == :syslog
- @tcp_client_options = options.delete(:tcp_client)
+ @tcp_client_options = tcp_client
raise "Unknown protocol #{@protocol}!" unless [:syslog, :tcp, :udp].include?(@protocol)
- @level_map = DEFAULT_LEVEL_MAP.dup
- @level_map.update(level_map) if level_map
-
# The syslog_protocol gem is required when logging over TCP or UDP.
if [:tcp, :udp].include?(@protocol)
begin
require 'syslog_protocol'
rescue LoadError
raise 'Missing gem: syslog_protocol. This gem is required when logging over TCP or UDP. To fix this error: gem install syslog_protocol'
end
# The net_tcp_client gem is required when logging over TCP.
if protocol == :tcp
- @tcp_client_options ||= {}
- @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
end
end
- super(options, &block)
+ super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
reopen
end
# After forking an active process call #reopen to re-open
# open the handles to resources
def reopen
case @protocol
when :syslog
- ::Syslog.open(application, @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::Processor.logger.clone
+ @tcp_client_options[:server] = "#{@server}:#{@port}"
@remote_syslog = Net::TCPClient.new(@tcp_client_options)
when :udp
@remote_syslog = UDPSocket.new
else
raise "Unsupported protocol: #{@protocol}"
@@ -213,13 +185,13 @@
when :syslog
# Since the Ruby Syslog API supports sprintf format strings, double up all existing '%'
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") }
+ @remote_syslog.retry_on_connection_failure { @remote_syslog.write("#{formatter.call(log, self)}\r\n") }
when :udp
- @remote_syslog.send syslog_packet_formatter(log), 0, @server, @port
+ @remote_syslog.send(formatter.call(log, self), 0, @server, @port)
else
raise "Unsupported protocol: #{protocol}"
end
true
end
@@ -227,69 +199,18 @@
# 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.
- # Only difference is the removal of the timestamp string since it is in the syslog packet.
- def call(log, logger)
- # Header with date, time, log level and process info
- message = "#{log.level_to_s} [#{log.process_info}]"
-
- # Tags
- message << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
-
- # Duration
- message << " (#{log.duration_human})" if log.duration
-
- # Class / app name
- message << " #{log.name}"
-
- # Log message
- message << " -- #{log.message}" if log.message
-
- # Payload
- if payload = log.payload_to_s
- message << ' -- ' << payload
- end
-
- # Exceptions
- if log.exception
- message << " -- Exception: #{log.exception.class}: #{log.exception.message}\n"
- message << log.backtrace_to_s
- end
- message
- end
-
- private
-
- # Extract Syslog formatter options
- def format_options(options, protocol, &block)
- opts = options.delete(:options)
- facility = options.delete(:facility)
- level_map = options.delete(:level_map)
- if formatter = options.delete(:formatter)
- extract_formatter(formatter)
+ # Returns [SemanticLogger::Formatters::Base] default formatter for this Appender depending on the protocal selected
+ def default_formatter
+ if protocol == :syslog
+ # Format is text output without the time
+ SemanticLogger::Formatters::Default.new(time_format: nil)
else
- case protocol
- when :syslog
- extract_formatter(syslog: {options: opts, facility: facility, level_map: level_map})
- when :tcp, :udp
- extract_formatter(syslog: {options: opts, facility: facility, level_map: level_map})
- end
+ SemanticLogger::Formatters::Syslog.new(facility: facility, level_map: level_map)
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 = host
- packet.facility = @facility
- packet.severity = @level_map[log.level]
- packet.tag = application.gsub(' ', '')
- packet.content = formatter.call(log, self)
- packet.time = log.time
- packet.to_s
- end
end
end
end