logplex-client 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|