begin require "net/tcp_client" rescue LoadError raise LoadError, 'Gem net_tcp_client is required for logging over TCP. Please add the gem "net_tcp_client" to your Gemfile.' end raise "Net::TCPClient v2.0 or greater is required to log over TCP" unless Net::TCPClient::VERSION.to_f >= 2.0 module SemanticLogger module Appender # TCP log appender. # # Log to a server over a TCP Socket. # By default messages are in JSON format. # # Features: # * JSON Formatted messages. # * SSL encryption. # * Transparently reconnect when a connection is lost. # # Example: # SemanticLogger.add_appender( # appender: :tcp, # server: 'server:3300', # ) # # Example, with connection retry options: # SemanticLogger.add_appender( # appender: :tcp, # server: 'server:3300', # connect_retry_interval: 0.1, # connect_retry_count: 5 # ) # # Example, with SSL enabled: # SemanticLogger.add_appender( # appender: :tcp, # server: 'server:3300', # ssl: true # ) # class Tcp < SemanticLogger::Subscriber attr_accessor :separator attr_reader :tcp_client # Create TCP log appender. # # Net::TCPClient Parameters: # :server [String] # URL of the server to connect to with port number # 'localhost:2000' # '192.168.1.10:80' # # :servers [Array of String] # Array of URL's of servers to connect to with port numbers # ['server1:2000', 'server2:2000'] # # The second server will only be attempted once the first server # cannot be connected to or has timed out on connect # A read failure or timeout will not result in switching to the second # server, only a connection failure or during an automatic reconnect # # :connect_timeout [Float] # Time in seconds to timeout when trying to connect to the server # A value of -1 will cause the connect wait time to be infinite # Default: Half of the :read_timeout ( 30 seconds ) # # :read_timeout [Float] # Time in seconds to timeout on read # Can be overridden by supplying a timeout in the read call # Default: 60 # # :write_timeout [Float] # Time in seconds to timeout on write # Can be overridden by supplying a timeout in the write call # Default: 60 # # :log_level [Symbol] # Optional: Set the logging level for the TCPClient # Any valid SemanticLogger log level: # :trace, :debug, :info, :warn, :error, :fatal # Default: SemanticLogger.default_level # # :buffered [Boolean] # Whether to use Nagle's Buffering algorithm (http://en.wikipedia.org/wiki/Nagle's_algorithm) # Recommend disabling for RPC style invocations where we don't want to wait for an # ACK from the server before sending the last partial segment # Buffering is recommended in a browser or file transfer style environment # where multiple sends are expected during a single response # Default: true # # :connect_retry_count [Integer] # Number of times to retry connecting when a connection fails # Default: 10 # # :connect_retry_interval [Float] # Number of seconds between connection retry attempts after the first failed attempt # Default: 0.5 # # :retry_count [Integer] # Number of times to retry when calling #retry_on_connection_failure # This is independent of :connect_retry_count which still applies with # connection failures. This retry controls upto how many times to retry the # supplied block should a connection failure occurr during the block # Default: 3 # # :on_connect [Proc] # Directly after a connection is established and before it is made available # for use this Block is invoked. # Typical Use Cases: # - Initialize per connection session sequence numbers # - Pass any authentication information to the server # - Perform a handshake with the server # # :policy [Symbol|Proc] # Specify the policy to use when connecting to servers. # :ordered # Select a server in the order supplied in the array, with the first # having the highest priority. The second server will only be connected # to if the first server is unreachable # :random # Randomly select a server from the list every time a connection # is established, including during automatic connection recovery. # :ping_time # FUTURE - Not implemented yet - Pull request anyone? # The server with the lowest ping time will be tried first # Proc: # When a Proc is supplied, it will be called passing in the list # of servers. The Proc must return one server name # Example: # :policy => Proc.new do |servers| # servers.last # end # Default: :ordered # # :close_on_error [True|False] # To prevent the connection from going into an inconsistent state # automatically close the connection if an error occurs # This includes a Read Timeout # Default: true # # Appender Parameters: # separator: [String] # Separator between every message # Default: "\n" # Note: The separator should not be something that could be output in the formatted log message. # # Common Appender Parameters: # application: [String] # Name of this application to appear in log messages. # Default: SemanticLogger.application # # host: [String] # Name of this host to appear in log messages. # Default: SemanticLogger.host # # 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. # Example: # SemanticLogger.add_appender( # appender: :tcp, # server: 'server:3300' # ) # # Example, with connection retry options: # SemanticLogger.add_appender( # appender: :tcp, # server: 'server:3300', # connect_retry_interval: 0.1, # connect_retry_count: 5 # ) def initialize(separator: "\n", level: nil, formatter: nil, filter: nil, application: nil, environment: nil, host: nil, metrics: false, **tcp_client_args, &block) @separator = separator @tcp_client_args = tcp_client_args # Use the internal logger so that errors with remote logging are only written locally. Net::TCPClient.logger = logger Net::TCPClient.logger.name = "Net::TCPClient" super(level: level, formatter: formatter, filter: filter, application: application, environment: environment, host: host, &block) reopen end # After forking an active process call #reopen to re-open the handles to resources. def reopen close @tcp_client = Net::TCPClient.new(**@tcp_client_args) end # Write the log using the specified protocol and server. def log(log) message = formatter.call(log, self) @tcp_client.retry_on_connection_failure do @tcp_client.write("#{message}#{separator}") end true end # Flush is called by the semantic_logger during shutdown. def flush @tcp_client&.flush end # Close is called during shutdown, or with reopen def close @tcp_client&.close end private # Returns [SemanticLogger::Formatters::Default] formatter default for this Appender def default_formatter SemanticLogger::Formatters::Json.new end end end end