logplex-client 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +13 -0
- data/Makefile +34 -0
- data/README.md +117 -0
- data/bin/logplex +12 -0
- data/lib/logplex/channel.rb +144 -0
- data/lib/logplex/client/backends/http.rb +192 -0
- data/lib/logplex/client/cli.rb +219 -0
- data/lib/logplex/client/version.rb +6 -0
- data/lib/logplex/client.rb +80 -0
- data/lib/logplex/drain.rb +45 -0
- data/lib/logplex/emitter.rb +79 -0
- data/lib/logplex/event.rb +8 -0
- data/lib/logplex/namespace.rb +8 -0
- data/lib/logplex/session.rb +72 -0
- data/lib/logplex/token.rb +42 -0
- data/logplex-client.gemspec +30 -0
- data/spec/documentation_spec.rb +41 -0
- data/spec/logplex/channel_spec.rb +79 -0
- data/spec/logplex/client_spec.rb +40 -0
- data/spec/logplex/session_spec.rb +32 -0
- data/spec/spec_setup.rb +2 -0
- metadata +140 -0
@@ -0,0 +1,219 @@
|
|
1
|
+
require 'clamp'
|
2
|
+
|
3
|
+
module Logplex
|
4
|
+
class Client
|
5
|
+
|
6
|
+
# The logplex client command-line interface
|
7
|
+
class CLI < Clamp::Command
|
8
|
+
|
9
|
+
# Generic error used for subclassing CLI-specific problems
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
# General 'this command failed' error
|
13
|
+
class CommandFailed < Error; end
|
14
|
+
|
15
|
+
option ["-v", "--version"], :flag, "Show logplex client version" do
|
16
|
+
puts "logplex-client #{VERSION}"
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
|
20
|
+
subcommand "channels:create", "Create a logplex channel" do
|
21
|
+
option "--logplex-url", "LOGPLEX_URL",
|
22
|
+
"The url to logplex, like 'https://user:pass@logplex.example.com/'",
|
23
|
+
:environment_variable => "LOGPLEX_URL", :required => true
|
24
|
+
parameter "NAME", "channel name"
|
25
|
+
parameter "[TOKEN] ...", "associated channel tokens"
|
26
|
+
def execute
|
27
|
+
print "creating channel... "
|
28
|
+
channel = client.create_channel(name)
|
29
|
+
puts "done"
|
30
|
+
|
31
|
+
puts "channel id: #{channel.id}"
|
32
|
+
if !token_list.nil? && !token_list.empty?
|
33
|
+
token_list.each do |token|
|
34
|
+
puts "creating token (#{token})"
|
35
|
+
channel.create_token(token)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
rescue => e
|
39
|
+
failure(e)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
subcommand "channels:get", "Get channel info" do
|
44
|
+
option "--logplex-url", "LOGPLEX_URL",
|
45
|
+
"The url to logplex, like 'https://user:pass@logplex.example.com/'",
|
46
|
+
:environment_variable => "LOGPLEX_URL", :required => true
|
47
|
+
parameter "CHANNEL", "channel id"
|
48
|
+
def execute
|
49
|
+
print "getting channel (#{channel})... "
|
50
|
+
chan = client.channel(channel)
|
51
|
+
puts "done"
|
52
|
+
puts
|
53
|
+
puts "id: #{chan.id}"
|
54
|
+
puts "no tokens" if chan.tokens.empty?
|
55
|
+
chan.tokens.each do |token|
|
56
|
+
puts "token: #{token.name} (#{token.id})"
|
57
|
+
end
|
58
|
+
puts "no drains" if chan.drains.empty?
|
59
|
+
chan.drains.each do |drain|
|
60
|
+
puts "drain: #{drain.drain_url} (#{drain.id})"
|
61
|
+
end
|
62
|
+
rescue => e
|
63
|
+
failure(e)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
subcommand "channels:delete", "Delete a channel and associated drains/tokens" do
|
68
|
+
option "--logplex-url", "LOGPLEX_URL",
|
69
|
+
"The url to logplex, like 'https://user:pass@logplex.example.com/'",
|
70
|
+
:environment_variable => "LOGPLEX_URL", :required => true
|
71
|
+
parameter "CHANNEL", "channel name or id"
|
72
|
+
def execute
|
73
|
+
print "deleting channel... "
|
74
|
+
client.channel(channel).destroy
|
75
|
+
puts "done"
|
76
|
+
rescue => e
|
77
|
+
failure(e)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
subcommand "tokens:create", "Add a token to a channel" do
|
82
|
+
option "--logplex-url", "LOGPLEX_URL",
|
83
|
+
"The url to logplex, like 'https://user:pass@logplex.example.com/'",
|
84
|
+
:environment_variable => "LOGPLEX_URL", :required => true
|
85
|
+
parameter "CHANNEL", "channel id"
|
86
|
+
parameter "NAME", "token name"
|
87
|
+
def execute
|
88
|
+
print "creating token... "
|
89
|
+
chan = client.channel(channel)
|
90
|
+
token = chan.create_token(name)
|
91
|
+
puts "done"
|
92
|
+
puts "#{name} => #{token.id}"
|
93
|
+
rescue => e
|
94
|
+
failure(e)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
subcommand "tokens:delete", "Remove a token from a channel" do
|
99
|
+
option "--logplex-url", "LOGPLEX_URL",
|
100
|
+
"The url to logplex, like 'https://user:pass@logplex.example.com/'",
|
101
|
+
:environment_variable => "LOGPLEX_URL", :required => true
|
102
|
+
parameter "CHANNEL", "channel id"
|
103
|
+
parameter "NAME", "token name"
|
104
|
+
def execute
|
105
|
+
# TODO(sissel): Logplex doesn't appear to have (or document?) this functionality.
|
106
|
+
# per http://logplex.herokuapp.com/ as of 2012/04/20
|
107
|
+
$stderr.puts "Logplex currently does not implement this functionality"
|
108
|
+
return
|
109
|
+
|
110
|
+
#found = false
|
111
|
+
#client.channel(channel).tokens.each do |token|
|
112
|
+
#if token.name == name
|
113
|
+
#puts "deleting token... "
|
114
|
+
#token.destroy
|
115
|
+
#found = true
|
116
|
+
#end
|
117
|
+
#end
|
118
|
+
#puts "no token found" if !found
|
119
|
+
rescue => e
|
120
|
+
failure(e)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
subcommand "drains:create", "Add a drain to a channel" do
|
125
|
+
option "--logplex-url", "LOGPLEX_URL",
|
126
|
+
"The url to logplex, like 'https://user:pass@logplex.example.com/'",
|
127
|
+
:environment_variable => "LOGPLEX_URL", :required => true
|
128
|
+
parameter "CHANNEL", "channel id"
|
129
|
+
|
130
|
+
# For now, due to a bug (or misdocumented feature) in logplex, a drain
|
131
|
+
# url is required at creation-time.
|
132
|
+
parameter "URL", "drain url (like 'syslog://..../')"
|
133
|
+
|
134
|
+
def execute
|
135
|
+
print "creating drain... "
|
136
|
+
chan = client.channel(channel)
|
137
|
+
drain = chan.create_drain(url)
|
138
|
+
puts "done"
|
139
|
+
rescue => e
|
140
|
+
failure(e)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
subcommand "drains:delete", "Remove a drain from a channel" do
|
145
|
+
option "--logplex-url", "LOGPLEX_URL",
|
146
|
+
"The url to logplex, like 'https://user:pass@logplex.example.com/'",
|
147
|
+
:environment_variable => "LOGPLEX_URL", :required => true
|
148
|
+
parameter "CHANNEL", "channel id"
|
149
|
+
parameter "DRAIN", "drain id or token"
|
150
|
+
def execute
|
151
|
+
found = false
|
152
|
+
channel = client.channel(channel)
|
153
|
+
channel.drains.each do |d|
|
154
|
+
if d.token == drain or d.id == drain
|
155
|
+
print "deleting drain... "
|
156
|
+
d.destroy
|
157
|
+
puts "done"
|
158
|
+
found = true
|
159
|
+
end
|
160
|
+
end
|
161
|
+
puts "no drain found" if !found
|
162
|
+
rescue => e
|
163
|
+
failure(e)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
subcommand "channels:logs", "fetch channel logs" do
|
168
|
+
option "--logplex-url", "LOGPLEX_URL",
|
169
|
+
"The url to logplex, like 'https://user:pass@logplex.example.com/'",
|
170
|
+
:environment_variable => "LOGPLEX_URL", :required => true
|
171
|
+
parameter "CHANNEL", "channel id"
|
172
|
+
option ["--source", "-s"], "SOURCE", "log source"
|
173
|
+
option ["--ps", "-p"], "PS", "log process"
|
174
|
+
option ["--num", "-n"], "NUM", "max number of logs to fetch"
|
175
|
+
option ["--tail", "-t"], :flag, "maintain a tail session of log stream"
|
176
|
+
option ["--chunk-size", "-c"], "CHUNKS_SIZE", "response chunk size", :default => Logplex::Session::DEFAULT_CHUNK_SIZE
|
177
|
+
|
178
|
+
def execute
|
179
|
+
opts = {}
|
180
|
+
opts[:source] = source if source
|
181
|
+
opts[:ps] = ps if ps
|
182
|
+
opts[:num] = num.to_i if num
|
183
|
+
opts[:tail] = true if tail?
|
184
|
+
opts[:chunk_size] = chunk_size.to_i if chunk_size
|
185
|
+
chan = client.channel(channel)
|
186
|
+
session = chan.create_session(opts)
|
187
|
+
session.each_event do |event|
|
188
|
+
puts event
|
189
|
+
end
|
190
|
+
rescue => e
|
191
|
+
failure(e)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
# Private: Get a logplex client instance.
|
198
|
+
#
|
199
|
+
# Subsequent calls to this method wil return the same object.
|
200
|
+
#
|
201
|
+
# Returns a Logplex::Client
|
202
|
+
def client
|
203
|
+
@client ||= Logplex::Client.new(logplex_url)
|
204
|
+
end # def client
|
205
|
+
|
206
|
+
# Private: Emit a failure.
|
207
|
+
def failure(e)
|
208
|
+
puts "failed"
|
209
|
+
|
210
|
+
if e.is_a?(Logplex::Client::Backends::HTTP::NotFound)
|
211
|
+
puts "! error: Object not found (#{e.to_s})"
|
212
|
+
else
|
213
|
+
puts "! error: #{e.to_s}"
|
214
|
+
end
|
215
|
+
exit(1)
|
216
|
+
end # def failure
|
217
|
+
end # class Logplex::Client::CLI
|
218
|
+
end # class Logplex::Client
|
219
|
+
end # module Logplex
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "logplex/namespace"
|
2
|
+
require "logplex/client/version"
|
3
|
+
require "logplex/client/backends/http"
|
4
|
+
require "logplex/channel"
|
5
|
+
require "logplex/session"
|
6
|
+
|
7
|
+
# Public: a logplex client.
|
8
|
+
#
|
9
|
+
# This class allows you to access logplex as a client; reading and writing
|
10
|
+
# logs, creating channels, tokens, etc.
|
11
|
+
#
|
12
|
+
# For logplex's HTTP API, see http://logplex.herokuapp.com
|
13
|
+
#
|
14
|
+
# Examples
|
15
|
+
#
|
16
|
+
# client = Logplex::Client.new("https://user:pass@logplex.example.com/")
|
17
|
+
# channel = client.create_channel("my-example-channel")
|
18
|
+
# TODO(sissel): Complete this example.
|
19
|
+
class Logplex::Client
|
20
|
+
# Public: Initialize a client pointing at a given url.
|
21
|
+
#
|
22
|
+
# url - a url (including user+pass) to your logplex service
|
23
|
+
#
|
24
|
+
# Examples
|
25
|
+
#
|
26
|
+
# Logplex::Client.new("https://user:pass@logplex.heroku.com/")
|
27
|
+
def initialize(url)
|
28
|
+
@url = url
|
29
|
+
@backend = Logplex::Client::Backends::HTTP.new(@url)
|
30
|
+
end # def initialize
|
31
|
+
|
32
|
+
# Public: Create a channel.
|
33
|
+
#
|
34
|
+
# Note: the 'name' of the channel lives in a global namespace. Choose wisely.
|
35
|
+
# Note: You must save the channel id if you wish to use the channel later.
|
36
|
+
#
|
37
|
+
# name - the string name to give to the channel being created.
|
38
|
+
#
|
39
|
+
# Examples
|
40
|
+
#
|
41
|
+
# client.create_channel("my-example-channel")
|
42
|
+
#
|
43
|
+
# Returns a {Logplex::Channel}
|
44
|
+
# Raises TODO(sissel): Raises what?
|
45
|
+
def create_channel(name)
|
46
|
+
# TODO(sissel): Call the API
|
47
|
+
result = @backend.create_channel(name)
|
48
|
+
return Logplex::Channel.new(@url, result[:channel_id])
|
49
|
+
end # def create_channel
|
50
|
+
|
51
|
+
# Public: Get a {Logplex::Channel} instance with a given id.
|
52
|
+
#
|
53
|
+
# If you have already created a channel, this is how you access it later.
|
54
|
+
#
|
55
|
+
# channel_id - the channel id (number)
|
56
|
+
#
|
57
|
+
# Returns a {Logplex::Channel}
|
58
|
+
# Raises TODO(sissel): ??? if the channel is not found
|
59
|
+
def channel(channel_id)
|
60
|
+
# This will throw an exception if it doesn't exist.
|
61
|
+
result = @backend.get_channel(channel_id)
|
62
|
+
chan = Logplex::Channel.new(@url, channel_id)
|
63
|
+
# Hack for now to push the result of get_channel into the Channel.
|
64
|
+
chan.instance_eval { @info = result }
|
65
|
+
return chan
|
66
|
+
end # def channel
|
67
|
+
|
68
|
+
# Public: Get a {Logplex::Session} instance with a given id.
|
69
|
+
#
|
70
|
+
# If you have already created a session, this is how you access it later.
|
71
|
+
#
|
72
|
+
# session_id - the session id (number)
|
73
|
+
#
|
74
|
+
# Returns a {Logplex::Session}
|
75
|
+
# Raises TODO(sissel): ??? if the session is not found
|
76
|
+
def session(session_id)
|
77
|
+
raise NotImplemented
|
78
|
+
end # def session
|
79
|
+
end # class Logplex::Client
|
80
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "logplex/namespace"
|
2
|
+
|
3
|
+
# Operations on a Logplex::Drain:
|
4
|
+
# Add a url to a drain: POST /v2/channels/<channel>/drains/<drain> { "url": ... }
|
5
|
+
# drain.use(url) ### I like 'use' better than 'drain.url = "url"'
|
6
|
+
# Delete a drain: DELETE /v2/channels/<channel>/drains/<drain>
|
7
|
+
# drain.destroy
|
8
|
+
#
|
9
|
+
class Logplex::Drain
|
10
|
+
def initialize(url, channel_id, drain_id, drain_token, drain_url=nil)
|
11
|
+
@url = url
|
12
|
+
@channel_id = channel_id
|
13
|
+
@id = drain_id
|
14
|
+
@token = drain_token
|
15
|
+
@drain_url = drain_url
|
16
|
+
@backend = Logplex::Client::Backends::HTTP.new(@url)
|
17
|
+
end # def initialize
|
18
|
+
|
19
|
+
# Public: Destroy this drain.
|
20
|
+
#
|
21
|
+
# Returns nothing
|
22
|
+
# Raises TODO(sissel): document exceptions
|
23
|
+
def destroy
|
24
|
+
@backend.delete_drain(@channel_id, @drain_id)
|
25
|
+
end # def destroy
|
26
|
+
|
27
|
+
# Public: Set the output of this drain to the given drain_url.
|
28
|
+
#
|
29
|
+
# drain_url - the url to drain logs to. Usually of form syslog://host:port/
|
30
|
+
#
|
31
|
+
# Returns nothing
|
32
|
+
# Raises TODO(sissel): Document exceptions
|
33
|
+
def drain_url=(drain_url)
|
34
|
+
# I don't really like this API feel, doing RPC calls in a setter feels like
|
35
|
+
# it breaks expectations.
|
36
|
+
@backend.set_drain_url(@channel_id, @drain_id, drain_url)
|
37
|
+
@target = drain_url
|
38
|
+
end # def use
|
39
|
+
|
40
|
+
# Public: Get the url for this drain.
|
41
|
+
attr_reader :drain_url
|
42
|
+
|
43
|
+
# Public: Get the id for this drain
|
44
|
+
attr_reader :id
|
45
|
+
end # class Logplex::Drain
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "logplex/namespace"
|
2
|
+
|
3
|
+
# A logging emitter. Best to create this with {Logplex::Token#emitter}
|
4
|
+
class Logplex::Emitter
|
5
|
+
|
6
|
+
# Default methods to private, see bottom of class for public method
|
7
|
+
# declarations
|
8
|
+
private
|
9
|
+
|
10
|
+
# Public: Create an emitter with the given target address and token.
|
11
|
+
def initialize(address, token)
|
12
|
+
@address = address
|
13
|
+
@token = token
|
14
|
+
|
15
|
+
connect
|
16
|
+
end # def initialize
|
17
|
+
|
18
|
+
# Public: Emit an event.
|
19
|
+
#
|
20
|
+
# procid - the process id emitting this message. A string, like "web.1"
|
21
|
+
# message - the message to emit in this event.
|
22
|
+
def emit(procid, message)
|
23
|
+
# FRAME: LENGTH PAYLOAD
|
24
|
+
# LENGTH: decimal value of the length of the payload
|
25
|
+
payload = serialize_syslogish(13, Time.now.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
26
|
+
Socket.gethostname, @token, procid,
|
27
|
+
# 'message id' is meaningless to us
|
28
|
+
"-",
|
29
|
+
# This extra "- " is due to a bug in logplex.
|
30
|
+
"- #{message}")
|
31
|
+
event = "#{payload.size} #{payload}"
|
32
|
+
@socket.syswrite(event)
|
33
|
+
end # def emit
|
34
|
+
|
35
|
+
|
36
|
+
# Private: connect to the remote address
|
37
|
+
#
|
38
|
+
# Returns nothing
|
39
|
+
# Raises TODO(sissel): ???
|
40
|
+
def connect
|
41
|
+
# TODO(sissel): Handle errors/reconnections
|
42
|
+
@socket.close unless @socket.nil?
|
43
|
+
host, port = @address.split(":")
|
44
|
+
@socket = TCPSocket.new(host, port.to_i)
|
45
|
+
end # def connect
|
46
|
+
|
47
|
+
# Private: serialize an RFC5424 message given some parameters
|
48
|
+
#
|
49
|
+
# pri - the syslog priority (this is facility combined with severity, number)
|
50
|
+
# timestamp - the RFC3339 timestamp (string)
|
51
|
+
# host - the hostname generating this message (string)
|
52
|
+
# appname - the application generating this message (string)
|
53
|
+
# procid - the process's id who is generating this message (string)
|
54
|
+
# msgid - the message id, usually just "-" (string)
|
55
|
+
# msg - the message (string)
|
56
|
+
#
|
57
|
+
# Returns the string encoding of an RFC5424 message based on the given
|
58
|
+
# arguments
|
59
|
+
def serialize_syslogish(pri, timestamp, host, appname, procid, msgid, msg)
|
60
|
+
# This protocol is RFC5424 layered on the message framing portion of
|
61
|
+
# RFC5425 (no ssl/tls)
|
62
|
+
#
|
63
|
+
# This method implements only the event formatting, not the framing.
|
64
|
+
#
|
65
|
+
# It is roughly:
|
66
|
+
# PAYLOAD: <PRI>VERSION TIMESTAMP HOST APPNAME PROCID MSGID MSG
|
67
|
+
# PRI: 0-191 (syslog pri combination of severity * 8 + facility
|
68
|
+
# VERSION: 1
|
69
|
+
# TIMESTAMP: RFC3339 time
|
70
|
+
# HOST: hostname
|
71
|
+
# APPNAME: the logplex channel token (t.<UUID>)
|
72
|
+
# PROCID: the process id, on heroku this is usually something like 'web.9'
|
73
|
+
# MSGID: usually null as "-"
|
74
|
+
return ["<#{pri}>1", timestamp, host, appname, procid, msgid, msg].join(" ")
|
75
|
+
end # def serialize_syslogish
|
76
|
+
|
77
|
+
# Explicitly declare methods public.
|
78
|
+
public(:initialize, :emit)
|
79
|
+
end # class Logstash::Emitter
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "logplex/namespace"
|
2
|
+
require "excon"
|
3
|
+
|
4
|
+
# Operations on a Logplex::Session:
|
5
|
+
# Get logs for a session - GET /sessions/<session>
|
6
|
+
# API> session.each_event do |event|
|
7
|
+
# # 'event' should be a nice object representation, not a string.
|
8
|
+
# of a session
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
|
12
|
+
# Public: A logplex event session.
|
13
|
+
#
|
14
|
+
# This class allows you to read events from logplex.
|
15
|
+
#
|
16
|
+
# You generally acquire an instance of this class through
|
17
|
+
# Logplex::Channel#create_session
|
18
|
+
class Logplex::Session
|
19
|
+
DEFAULT_CHUNK_SIZE = 1024
|
20
|
+
|
21
|
+
# Public: Initialize a logplex session.
|
22
|
+
#
|
23
|
+
# url - the url to the session, usually of the form:
|
24
|
+
# https://user:pass@logplex/sessions/session-id
|
25
|
+
#
|
26
|
+
# session_settings - a hash of arguments; see
|
27
|
+
# {Logplex::Channel#create_session} for values.
|
28
|
+
def initialize(url, session_settings)
|
29
|
+
@url = url
|
30
|
+
|
31
|
+
if session_settings[:tail]
|
32
|
+
@tail = true
|
33
|
+
else
|
34
|
+
@tail = false
|
35
|
+
@limit = session_settings[:num]
|
36
|
+
end
|
37
|
+
@chunk_size = session_settings[:chunk_size] || DEFAULT_CHUNK_SIZE
|
38
|
+
end # def initialize
|
39
|
+
|
40
|
+
# Public: iterate over events received from this logplex session
|
41
|
+
#
|
42
|
+
# Yields Logplex::Event objects, one per event.
|
43
|
+
# Returns nothing
|
44
|
+
# Raises TODO(sissel): ???
|
45
|
+
def each_event(&block)
|
46
|
+
connection = Excon.new(@url, :chunk_size => @chunk_size)
|
47
|
+
|
48
|
+
@buffer = ""
|
49
|
+
@event_count = 0
|
50
|
+
|
51
|
+
process_response = lambda do |chunk, remaining_bytes, total_bytes|
|
52
|
+
@buffer += chunk
|
53
|
+
|
54
|
+
events = @buffer.split("\n")
|
55
|
+
if @buffer[-1] != "\n"
|
56
|
+
@buffer = events.last
|
57
|
+
events = events[0 .. -2]
|
58
|
+
else
|
59
|
+
@buffer = ""
|
60
|
+
end
|
61
|
+
|
62
|
+
events.each do |line|
|
63
|
+
@event_count += 1
|
64
|
+
block.call(line)
|
65
|
+
return if !@tail && @limit < @event_count
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
response = connection.get(:response_block => process_response)
|
70
|
+
raise "Error: #{response.body}" if response.status != 200
|
71
|
+
end # def each_block
|
72
|
+
end # class Logplex::Session
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "logplex/namespace"
|
2
|
+
require "logplex/emitter"
|
3
|
+
|
4
|
+
# Operations on a Logplex::Token:
|
5
|
+
# Publish an event:
|
6
|
+
# API> token.publish(message)
|
7
|
+
#
|
8
|
+
#
|
9
|
+
|
10
|
+
# A logplex token is required for publishing events to logplex.
|
11
|
+
#
|
12
|
+
# To create a token, see Logplex::Channel#create_token.
|
13
|
+
class Logplex::Token
|
14
|
+
# Public: initialize a Logplex::Token
|
15
|
+
#
|
16
|
+
# url - the url to logplex; see Logplex::Client.new for more info
|
17
|
+
# channel_id - the channel id to create a token on; see Logplex::Channel for
|
18
|
+
# more info
|
19
|
+
# name - the string name of this token. This name will appear in logplex
|
20
|
+
# output (drains and log sessions) as the application name.
|
21
|
+
# token_id - the token; usually in the form 't.SOME-UUID-VALUE'
|
22
|
+
def initialize(url, channel_id, name, token_id)
|
23
|
+
@url = url
|
24
|
+
@channel_id = channel_id
|
25
|
+
@name = name
|
26
|
+
@id = token_id
|
27
|
+
end # def initialize
|
28
|
+
|
29
|
+
attr_accessor :name
|
30
|
+
attr_accessor :id
|
31
|
+
attr_reader :channel_id
|
32
|
+
attr_reader :url
|
33
|
+
|
34
|
+
# Public: Get an emitter suitable for publishing events with this token.
|
35
|
+
#
|
36
|
+
# Returns a Logplex::Emitter
|
37
|
+
def emitter
|
38
|
+
address = [URI.parse(@url).host, 601].join(":")
|
39
|
+
@emitter ||= Logplex::Emitter.new(address, @id)
|
40
|
+
return @emitter
|
41
|
+
end # def emitter
|
42
|
+
end # class Logplex::Token
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path("../lib/logplex/client/version", __FILE__)
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Jacob Vorreuter", "Jordan Sissel"]
|
7
|
+
gem.email = ["jacob.vorreuter@gmail.com", "jls@heroku.com"]
|
8
|
+
gem.description = %q{A client and library for Logplex}
|
9
|
+
gem.summary = %q{A client and library for Logplex}
|
10
|
+
gem.homepage = "https://github.com/heroku/logplex-client"
|
11
|
+
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
gem.name = "logplex-client"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = Logplex::Client::VERSION
|
18
|
+
|
19
|
+
# For command line flag parsing, etc.
|
20
|
+
gem.add_dependency "clamp", "~> 0.4.0"
|
21
|
+
|
22
|
+
# For json parsing
|
23
|
+
gem.add_dependency "multi_json"
|
24
|
+
|
25
|
+
# For HTTP stuff that RestClient can't handle
|
26
|
+
gem.add_dependency "excon", "~> 0.20.1"
|
27
|
+
|
28
|
+
# For general HTTP access to REST-ish APIs
|
29
|
+
gem.add_dependency "rest-client"
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "yard"
|
3
|
+
|
4
|
+
class MissingDocumentation < StandardError; end
|
5
|
+
|
6
|
+
describe "this project" do
|
7
|
+
before do
|
8
|
+
# Use YARD to parse all ruby files found in '../lib'
|
9
|
+
libdir = File.join(File.dirname(__FILE__), "..", "lib")
|
10
|
+
YARD::Registry.load(Dir.glob(File.join(libdir, "**", "*.rb")))
|
11
|
+
@registry = YARD::Registry.all
|
12
|
+
end
|
13
|
+
|
14
|
+
it "must have all classes, modules, and constants documented" do
|
15
|
+
# YARD's parser works best in ruby 1.9.x, so skip 1.8.x
|
16
|
+
skip if RUBY_VERSION < "1.9.2"
|
17
|
+
# Note, the 'find the undocumented things' code here is
|
18
|
+
# copied mostly from: YARD 0.7.5's lib/yard/cli/stats.rb
|
19
|
+
#
|
20
|
+
# Find all undocumented classes, modules, and constants
|
21
|
+
undocumented = @registry.select do |o|
|
22
|
+
[:class, :module, :constant].include?(o.type) && o.docstring.blank?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Find all undocumented methods
|
26
|
+
methods = @registry.select { |m| m.type == :method }
|
27
|
+
methods.reject! { |m| m.is_alias? || !m.is_explicit? }
|
28
|
+
undocumented += methods.select do |m|
|
29
|
+
m.docstring.blank? && !m.overridden_method
|
30
|
+
end
|
31
|
+
|
32
|
+
if (undocumented.length > 0)
|
33
|
+
message = ["The following are not documented"]
|
34
|
+
undocumented.each do |o|
|
35
|
+
message << "* #{o.type.to_s} #{o.to_s} <#{o.file}:#{o.line}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
raise MissingDocumentation.new(message.join("\n"))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|