Class: RightSupport::Log::Syslog::Remote

Inherits:
Object
  • Object
show all
Defined in:
lib/right_support/log/syslog/remote.rb

Overview

partially implements the Syslog interface for a remote syslog connection. the Syslog.open method is restricted to only using the ‘/dev/log’ socket (or darwin equivalent).

Defined Under Namespace

Modules: Severity Classes: RemoteSyslogError

Instance Method Summary collapse

Constructor Details

#initialize(uri, program_name, facility, options = {}) ⇒ Remote

Returns a new instance of Remote.



56
57
58
59
60
61
62
63
64
65
66
# File 'lib/right_support/log/syslog/remote.rb', line 56

def initialize(uri, program_name, facility, options = {})
  @uri = ::URI.parse(uri.to_s)
  @program_name = program_name
  @facility = facility
  @identity = "#{program_name}[#{$$}]"
  @hostname = ::Socket.gethostname.split('.').first
  @mutex = ::Mutex.new
  @socket = nil
  @error_handler = options[:error_handler]
  @error_mode = false
end

Instance Method Details

#alert(message) ⇒ Object



125
126
127
# File 'lib/right_support/log/syslog/remote.rb', line 125

def alert(message)
  log(@facility, Severity::ALERT, message)
end

#closeObject



111
112
113
114
115
116
117
118
119
# File 'lib/right_support/log/syslog/remote.rb', line 111

def close
  if @socket && !@socket.closed?
    @socket.close
  end
rescue => e
  STDERR.puts("Failed to close syslog socket: #{e.message}")
ensure
  @socket = nil
end

#connectObject



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/right_support/log/syslog/remote.rb', line 68

def connect
  close if @socket
  opener = nil
  case @uri.scheme
  when 'tcp'
    opener = lambda do
      ::TCPSocket.new(@uri.host, @uri.port)
    end
  when 'udp'
    opener = lambda do
      s = ::UDPSocket.new
      s.connect(@uri.host, @uri.port)
      s
    end
  else
    raise RemoteSyslogError, "Unxpected URI scheme: #{@uri.scheme}"
  end

  # backoff/retry connection. each log call will attempt to reconnect on
  # write failure but only the first will backoff/retry the connection.
  # the issue is to try and keep the service running and logging while
  # syslog is being restarted for routine maintainance. if syslog is offline
  # indefinitely then the app must continue without slowing. the caller can
  # provide an error_handler to perform graceful shutdown, etc.
  sleeper = 1
  attempts = 0
  begin
    @socket = opener.call
  rescue => e
    # do not repeat backoff/retry when already in error mode.
    raise if @error_mode
    attempts += 1
    if attempts <= 6   # about one minute
      STDERR.puts("Sleeping #{sleeper}s due to failure to (re)connect to syslog: #{e.message}\n")
      sleep(sleeper)
      sleeper *= 2
      retry
    else
      raise
    end
  end
end

#crit(message) ⇒ Object



129
130
131
# File 'lib/right_support/log/syslog/remote.rb', line 129

def crit(message)
  log(@facility, Severity::CRIT, message)
end

#debug(message) ⇒ Object



149
150
151
# File 'lib/right_support/log/syslog/remote.rb', line 149

def debug(message)
  log(@facility, Severity::DEBUG, message)
end

#emerg(message) ⇒ Object



121
122
123
# File 'lib/right_support/log/syslog/remote.rb', line 121

def emerg(message)
  log(@facility, Severity::EMERG, message)
end

#err(message) ⇒ Object



133
134
135
# File 'lib/right_support/log/syslog/remote.rb', line 133

def err(message)
  log(@facility, Severity::ERR, message)
end

#info(message) ⇒ Object



145
146
147
# File 'lib/right_support/log/syslog/remote.rb', line 145

def info(message)
  log(@facility, Severity::INFO, message)
end

#log(facility, severity, message) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/right_support/log/syslog/remote.rb', line 153

def log(facility, severity, message)
  protocol_message = "<#{(facility & 0xf8) | (severity & 0x07)}> "\
    "#{::Time.now.strftime('%b %d %H:%M:%S')} " \
    "#{@hostname} " \
    "#{@identity}" \
    ": " \
    "#{message.gsub("\n", '#012')}\n"
  @mutex.synchronize do
    last_error = nil
    begin
      @socket.write(protocol_message)
    rescue => e
      if reconnect
        begin
          @socket.write(protocol_message)
        rescue => e
          last_error = e
        end
      else
        last_error = e
      end
    end
    if last_error
      # we are only here if reconnection attempts have timed out at least
      # once. the timeout is skipped when we are already in error mode.
      if @error_mode
        # no need to repeat the warning or invoke callback for each
        # failure to write.
        ::STDERR.puts(protocol_message)
      else
        @error_mode = true
        msg = <<EOF
WARNING: Failed writing to syslog: #{last_error.message}
Dumping raw messages to stderr until rsyslog connection is restored.
EOF
        ::STDERR.puts(msg)
        ::STDERR.puts(protocol_message)

        # the caller may want to gracefully shutdown, etc. graceful shutdown
        # will trigger alerts whereas silently ignoring failure to write to
        # syslog can lead to other problems.
        @error_handler.call(last_error) if @error_handler
      end
    else
      @error_mode = false
    end
  end
  true
end

#notice(message) ⇒ Object



141
142
143
# File 'lib/right_support/log/syslog/remote.rb', line 141

def notice(message)
  log(@facility, Severity::NOTICE, message)
end

#warning(message) ⇒ Object



137
138
139
# File 'lib/right_support/log/syslog/remote.rb', line 137

def warning(message)
  log(@facility, Severity::WARNING, message)
end