Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
632974ed
Commit
632974ed
authored
Mar 12, 2020
by
James Fargher
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'ak/pagination' into 'master'
Add pagination to logs API See merge request gitlab-org/gitlab!26464
parents
15f0e4b2
4c8931cb
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
186 additions
and
30 deletions
+186
-30
app/controllers/projects/logs_controller.rb
app/controllers/projects/logs_controller.rb
+1
-1
app/services/pod_logs/base_service.rb
app/services/pod_logs/base_service.rb
+5
-3
app/services/pod_logs/elasticsearch_service.rb
app/services/pod_logs/elasticsearch_service.rb
+22
-6
lib/gitlab/elasticsearch/logs.rb
lib/gitlab/elasticsearch/logs.rb
+41
-6
locale/gitlab.pot
locale/gitlab.pot
+3
-0
spec/fixtures/lib/elasticsearch/query_with_cursor.json
spec/fixtures/lib/elasticsearch/query_with_cursor.json
+43
-0
spec/lib/gitlab/elasticsearch/logs_spec.rb
spec/lib/gitlab/elasticsearch/logs_spec.rb
+20
-11
spec/services/pod_logs/elasticsearch_service_spec.rb
spec/services/pod_logs/elasticsearch_service_spec.rb
+51
-3
No files found.
app/controllers/projects/logs_controller.rb
View file @
632974ed
...
...
@@ -48,7 +48,7 @@ module Projects
end
def
elasticsearch_params
params
.
permit
(
:container_name
,
:pod_name
,
:search
,
:start
,
:end
)
params
.
permit
(
:container_name
,
:pod_name
,
:search
,
:start
,
:end
,
:cursor
)
end
def
environment
...
...
app/services/pod_logs/base_service.rb
View file @
632974ed
...
...
@@ -10,8 +10,6 @@ module PodLogs
CACHE_KEY_GET_POD_LOG
=
'get_pod_log'
K8S_NAME_MAX_LENGTH
=
253
SUCCESS_RETURN_KEYS
=
%i(status logs pod_name container_name pods)
.
freeze
def
id
cluster
.
id
end
...
...
@@ -49,6 +47,10 @@ module PodLogs
%w(pod_name container_name)
end
def
success_return_keys
%i(status logs pod_name container_name pods)
end
def
check_arguments
(
result
)
return
error
(
_
(
'Cluster does not exist'
))
if
cluster
.
nil?
return
error
(
_
(
'Namespace is empty'
))
if
namespace
.
blank?
...
...
@@ -122,7 +124,7 @@ module PodLogs
end
def
filter_return_keys
(
result
)
result
.
slice
(
*
SUCCESS_RETURN_KEYS
)
result
.
slice
(
*
success_return_keys
)
end
def
filter_params
(
params
)
...
...
app/services/pod_logs/elasticsearch_service.rb
View file @
632974ed
...
...
@@ -10,6 +10,7 @@ module PodLogs
:check_container_name
,
:check_times
,
:check_search
,
:check_cursor
,
:pod_logs
,
:filter_return_keys
...
...
@@ -18,7 +19,11 @@ module PodLogs
private
def
valid_params
%w(pod_name container_name search start end)
super
+
%w(search start end cursor)
end
def
success_return_keys
super
+
%i(cursor)
end
def
check_times
(
result
)
...
...
@@ -36,19 +41,28 @@ module PodLogs
success
(
result
)
end
def
check_cursor
(
result
)
result
[
:cursor
]
=
params
[
'cursor'
]
if
params
.
key?
(
'cursor'
)
success
(
result
)
end
def
pod_logs
(
result
)
client
=
cluster
&
.
application_elastic_stack
&
.
elasticsearch_client
return
error
(
_
(
'Unable to connect to Elasticsearch'
))
unless
client
res
ult
[
:logs
]
=
::
Gitlab
::
Elasticsearch
::
Logs
.
new
(
client
).
pod_logs
(
res
ponse
=
::
Gitlab
::
Elasticsearch
::
Logs
.
new
(
client
).
pod_logs
(
namespace
,
result
[
:pod_name
],
result
[
:container_name
],
result
[
:search
],
result
[
:start
],
result
[
:end
]
container_name:
result
[
:container_name
],
search:
result
[
:search
],
start_time:
result
[
:start
],
end_time:
result
[
:end
],
cursor:
result
[
:cursor
]
)
result
.
merge!
(
response
)
success
(
result
)
rescue
Elasticsearch
::
Transport
::
Transport
::
ServerError
=>
e
::
Gitlab
::
ErrorTracking
.
track_exception
(
e
)
...
...
@@ -58,6 +72,8 @@ module PodLogs
# there is no method on the exception other than the class name to determine the type of error encountered.
status_code:
e
.
class
.
name
.
split
(
'::'
).
last
})
rescue
::
Gitlab
::
Elasticsearch
::
Logs
::
InvalidCursor
error
(
_
(
'Invalid cursor value provided'
))
end
end
end
lib/gitlab/elasticsearch/logs.rb
View file @
632974ed
...
...
@@ -3,6 +3,8 @@
module
Gitlab
module
Elasticsearch
class
Logs
InvalidCursor
=
Class
.
new
(
RuntimeError
)
# How many log lines to fetch in a query
LOGS_LIMIT
=
500
...
...
@@ -10,7 +12,7 @@ module Gitlab
@client
=
client
end
def
pod_logs
(
namespace
,
pod_name
,
container_name
=
nil
,
search
=
nil
,
start_time
=
nil
,
end_time
=
nil
)
def
pod_logs
(
namespace
,
pod_name
,
container_name
:
nil
,
search:
nil
,
start_time:
nil
,
end_time:
nil
,
cursor:
nil
)
query
=
{
bool:
{
must:
[]
}
}.
tap
do
|
q
|
filter_pod_name
(
q
,
pod_name
)
filter_namespace
(
q
,
namespace
)
...
...
@@ -19,7 +21,7 @@ module Gitlab
filter_times
(
q
,
start_time
,
end_time
)
end
body
=
build_body
(
query
)
body
=
build_body
(
query
,
cursor
)
response
=
@client
.
search
body:
body
format_response
(
response
)
...
...
@@ -27,8 +29,8 @@ module Gitlab
private
def
build_body
(
query
)
{
def
build_body
(
query
,
cursor
=
nil
)
body
=
{
query:
query
,
# reverse order so we can query N-most recent records
sort:
[
...
...
@@ -40,6 +42,12 @@ module Gitlab
# fixed limit for now, we should support paginated queries
size:
::
Gitlab
::
Elasticsearch
::
Logs
::
LOGS_LIMIT
}
unless
cursor
.
nil?
body
[
:search_after
]
=
decode_cursor
(
cursor
)
end
body
end
def
filter_pod_name
(
query
,
pod_name
)
...
...
@@ -100,7 +108,9 @@ module Gitlab
end
def
format_response
(
response
)
result
=
response
.
fetch
(
"hits"
,
{}).
fetch
(
"hits"
,
[]).
map
do
|
hit
|
results
=
response
.
fetch
(
"hits"
,
{}).
fetch
(
"hits"
,
[])
last_result
=
results
.
last
results
=
results
.
map
do
|
hit
|
{
timestamp:
hit
[
"_source"
][
"@timestamp"
],
message:
hit
[
"_source"
][
"message"
]
...
...
@@ -108,7 +118,32 @@ module Gitlab
end
# we queried for the N-most recent records but we want them ordered oldest to newest
result
.
reverse
{
logs:
results
.
reverse
,
cursor:
last_result
.
nil?
?
nil
:
encode_cursor
(
last_result
[
"sort"
])
}
end
# we want to hide the implementation details of the search_after parameter from the frontend
# behind a single easily transmitted value
def
encode_cursor
(
obj
)
obj
.
join
(
','
)
end
def
decode_cursor
(
obj
)
cursor
=
obj
.
split
(
','
).
map
(
&
:to_i
)
unless
valid_cursor
(
cursor
)
raise
InvalidCursor
,
"invalid cursor format"
end
cursor
end
def
valid_cursor
(
cursor
)
cursor
.
instance_of?
(
Array
)
&&
cursor
.
length
==
2
&&
cursor
.
map
{
|
i
|
i
.
instance_of?
(
Integer
)}.
reduce
(
:&
)
end
end
end
...
...
locale/gitlab.pot
View file @
632974ed
...
...
@@ -10892,6 +10892,9 @@ msgstr ""
msgid "Invalid URL"
msgstr ""
msgid "Invalid cursor value provided"
msgstr ""
msgid "Invalid date"
msgstr ""
...
...
spec/fixtures/lib/elasticsearch/query_with_cursor.json
0 → 100644
View file @
632974ed
{
"query"
:
{
"bool"
:
{
"must"
:
[
{
"match_phrase"
:
{
"kubernetes.pod.name"
:
{
"query"
:
"production-6866bc8974-m4sk4"
}
}
},
{
"match_phrase"
:
{
"kubernetes.namespace"
:
{
"query"
:
"autodevops-deploy-9-production"
}
}
}
]
}
},
"sort"
:
[
{
"@timestamp"
:
{
"order"
:
"desc"
}
},
{
"offset"
:
{
"order"
:
"desc"
}
}
],
"search_after"
:
[
9999934
,
1572449784442
],
"_source"
:
[
"@timestamp"
,
"message"
],
"size"
:
500
}
spec/lib/gitlab/elasticsearch/logs_spec.rb
View file @
632974ed
...
...
@@ -20,6 +20,7 @@ describe Gitlab::Elasticsearch::Logs do
let
(
:search
)
{
"foo +bar "
}
let
(
:start_time
)
{
"2019-12-13T14:35:34.034Z"
}
let
(
:end_time
)
{
"2019-12-13T14:35:34.034Z"
}
let
(
:cursor
)
{
"9999934,1572449784442"
}
let
(
:body
)
{
JSON
.
parse
(
fixture_file
(
'lib/elasticsearch/query.json'
))
}
let
(
:body_with_container
)
{
JSON
.
parse
(
fixture_file
(
'lib/elasticsearch/query_with_container.json'
))
}
...
...
@@ -27,6 +28,7 @@ describe Gitlab::Elasticsearch::Logs do
let
(
:body_with_times
)
{
JSON
.
parse
(
fixture_file
(
'lib/elasticsearch/query_with_times.json'
))
}
let
(
:body_with_start_time
)
{
JSON
.
parse
(
fixture_file
(
'lib/elasticsearch/query_with_start_time.json'
))
}
let
(
:body_with_end_time
)
{
JSON
.
parse
(
fixture_file
(
'lib/elasticsearch/query_with_end_time.json'
))
}
let
(
:body_with_cursor
)
{
JSON
.
parse
(
fixture_file
(
'lib/elasticsearch/query_with_cursor.json'
))
}
RSpec
::
Matchers
.
define
:a_hash_equal_to_json
do
|
expected
|
match
do
|
actual
|
...
...
@@ -39,42 +41,49 @@ describe Gitlab::Elasticsearch::Logs do
expect
(
client
).
to
receive
(
:search
).
with
(
body:
a_hash_equal_to_json
(
body
)).
and_return
(
es_response
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
)
expect
(
result
).
to
eq
(
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
]
)
expect
(
result
).
to
eq
(
logs:
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
],
cursor:
cursor
)
end
it
'can further filter the logs by container name'
do
expect
(
client
).
to
receive
(
:search
).
with
(
body:
a_hash_equal_to_json
(
body_with_container
)).
and_return
(
es_response
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
container_name
)
expect
(
result
).
to
eq
(
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
]
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
container_name
:
container_name
)
expect
(
result
).
to
eq
(
logs:
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
],
cursor:
cursor
)
end
it
'can further filter the logs by search'
do
expect
(
client
).
to
receive
(
:search
).
with
(
body:
a_hash_equal_to_json
(
body_with_search
)).
and_return
(
es_response
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
nil
,
search
)
expect
(
result
).
to
eq
(
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
]
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
search:
search
)
expect
(
result
).
to
eq
(
logs:
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
],
cursor:
cursor
)
end
it
'can further filter the logs by start_time and end_time'
do
expect
(
client
).
to
receive
(
:search
).
with
(
body:
a_hash_equal_to_json
(
body_with_times
)).
and_return
(
es_response
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
nil
,
nil
,
start_time
,
end_time
)
expect
(
result
).
to
eq
(
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
]
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
start_time:
start_time
,
end_time:
end_time
)
expect
(
result
).
to
eq
(
logs:
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
],
cursor:
cursor
)
end
it
'can further filter the logs by only start_time'
do
expect
(
client
).
to
receive
(
:search
).
with
(
body:
a_hash_equal_to_json
(
body_with_start_time
)).
and_return
(
es_response
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
nil
,
nil
,
start_time
)
expect
(
result
).
to
eq
(
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
]
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
start_time:
start_time
)
expect
(
result
).
to
eq
(
logs:
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
],
cursor:
cursor
)
end
it
'can further filter the logs by only end_time'
do
expect
(
client
).
to
receive
(
:search
).
with
(
body:
a_hash_equal_to_json
(
body_with_end_time
)).
and_return
(
es_response
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
nil
,
nil
,
nil
,
end_time
)
expect
(
result
).
to
eq
([
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
])
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
end_time:
end_time
)
expect
(
result
).
to
eq
(
logs:
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
],
cursor:
cursor
)
end
it
'can search after a cursor'
do
expect
(
client
).
to
receive
(
:search
).
with
(
body:
a_hash_equal_to_json
(
body_with_cursor
)).
and_return
(
es_response
)
result
=
subject
.
pod_logs
(
namespace
,
pod_name
,
cursor:
cursor
)
expect
(
result
).
to
eq
(
logs:
[
es_message_4
,
es_message_3
,
es_message_2
,
es_message_1
],
cursor:
cursor
)
end
end
end
spec/services/pod_logs/elasticsearch_service_spec.rb
View file @
632974ed
...
...
@@ -11,6 +11,7 @@ describe ::PodLogs::ElasticsearchService do
let
(
:search
)
{
'foo -bar'
}
let
(
:start_time
)
{
'2019-01-02T12:13:14+02:00'
}
let
(
:end_time
)
{
'2019-01-03T12:13:14+02:00'
}
let
(
:cursor
)
{
'9999934,1572449784442'
}
let
(
:params
)
{
{}
}
let
(
:expected_logs
)
do
[
...
...
@@ -116,6 +117,36 @@ describe ::PodLogs::ElasticsearchService do
end
end
describe
'#check_cursor'
do
context
'with cursor provided and valid'
do
let
(
:params
)
do
{
'cursor'
=>
cursor
}
end
it
'returns success with cursor'
do
result
=
subject
.
send
(
:check_cursor
,
{})
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
result
[
:cursor
]).
to
eq
(
cursor
)
end
end
context
'with cursor not provided'
do
let
(
:params
)
do
{}
end
it
'returns success with nothing else'
do
result
=
subject
.
send
(
:check_cursor
,
{})
expect
(
result
.
keys
.
length
).
to
eq
(
1
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
end
end
end
describe
'#pod_logs'
do
let
(
:result_arg
)
do
{
...
...
@@ -123,9 +154,11 @@ describe ::PodLogs::ElasticsearchService do
container_name:
container_name
,
search:
search
,
start:
start_time
,
end:
end_time
end:
end_time
,
cursor:
cursor
}
end
let
(
:expected_cursor
)
{
'9999934,1572449784442'
}
before
do
create
(
:clusters_applications_elastic_stack
,
:installed
,
cluster:
cluster
)
...
...
@@ -137,13 +170,14 @@ describe ::PodLogs::ElasticsearchService do
.
and_return
(
Elasticsearch
::
Transport
::
Client
.
new
)
allow_any_instance_of
(
::
Gitlab
::
Elasticsearch
::
Logs
)
.
to
receive
(
:pod_logs
)
.
with
(
namespace
,
pod_name
,
container_name
,
search
,
start_time
,
end_time
)
.
and_return
(
expected_logs
)
.
with
(
namespace
,
pod_name
,
container_name
:
container_name
,
search:
search
,
start_time:
start_time
,
end_time:
end_time
,
cursor:
cursor
)
.
and_return
(
{
logs:
expected_logs
,
cursor:
expected_cursor
}
)
result
=
subject
.
send
(
:pod_logs
,
result_arg
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
expect
(
result
[
:logs
]).
to
eq
(
expected_logs
)
expect
(
result
[
:cursor
]).
to
eq
(
expected_cursor
)
end
it
'returns an error when ES is unreachable'
do
...
...
@@ -170,5 +204,19 @@ describe ::PodLogs::ElasticsearchService do
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
eq
(
'Elasticsearch returned status code: ServiceUnavailable'
)
end
it
'handles cursor errors from elasticsearch'
do
allow_any_instance_of
(
::
Clusters
::
Applications
::
ElasticStack
)
.
to
receive
(
:elasticsearch_client
)
.
and_return
(
Elasticsearch
::
Transport
::
Client
.
new
)
allow_any_instance_of
(
::
Gitlab
::
Elasticsearch
::
Logs
)
.
to
receive
(
:pod_logs
)
.
and_raise
(
::
Gitlab
::
Elasticsearch
::
Logs
::
InvalidCursor
.
new
)
result
=
subject
.
send
(
:pod_logs
,
result_arg
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:message
]).
to
eq
(
'Invalid cursor value provided'
)
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment