Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Framasoft
PeerTube
PeerTube
Commits
64553e88
Verified
Commit
64553e88
authored
Oct 20, 2021
by
Chocobozzz
Browse files
Add ability to filter logs by tags
parent
12437298
Changes
10
Hide whitespace changes
Inline
Side-by-side
client/src/app/+admin/system/logs/logs.component.html
View file @
64553e88
...
...
@@ -28,6 +28,8 @@
</ng-option>
</ng-select>
<my-select-tags
i18n-placeholder
placeholder=
"Filter logs by tags"
[(ngModel)]=
"tagsOneOf"
(ngModelChange)=
"refresh()"
></my-select-tags>
<my-button
i18n-label
label=
"Refresh"
icon=
"refresh"
(click)=
"refresh()"
></my-button>
</div>
...
...
client/src/app/+admin/system/logs/logs.component.scss
View file @
64553e88
...
...
@@ -52,9 +52,7 @@
@include
peertube-select-container
(
150px
);
}
my-button
,
.peertube-select-container
,
ng-select
{
>
*
{
@include
margin-left
(
10px
);
}
}
...
...
client/src/app/+admin/system/logs/logs.component.ts
View file @
64553e88
...
...
@@ -23,6 +23,7 @@ export class LogsComponent implements OnInit {
startDate
:
string
level
:
LogLevel
logType
:
'
audit
'
|
'
standard
'
tagsOneOf
:
string
[]
=
[]
constructor
(
private
logsService
:
LogsService
,
...
...
@@ -51,20 +52,28 @@ export class LogsComponent implements OnInit {
load
()
{
this
.
loading
=
true
this
.
logsService
.
getLogs
({
isAuditLog
:
this
.
isAuditLog
(),
level
:
this
.
level
,
startDate
:
this
.
startDate
})
.
subscribe
({
next
:
logs
=>
{
this
.
logs
=
logs
setTimeout
(()
=>
{
this
.
logsElement
.
nativeElement
.
scrollIntoView
({
block
:
'
end
'
,
inline
:
'
nearest
'
})
})
},
const
tagsOneOf
=
this
.
tagsOneOf
.
length
!==
0
?
this
.
tagsOneOf
:
undefined
this
.
logsService
.
getLogs
({
isAuditLog
:
this
.
isAuditLog
(),
level
:
this
.
level
,
startDate
:
this
.
startDate
,
tagsOneOf
}).
subscribe
({
next
:
logs
=>
{
this
.
logs
=
logs
setTimeout
(()
=>
{
this
.
logsElement
.
nativeElement
.
scrollIntoView
({
block
:
'
end
'
,
inline
:
'
nearest
'
})
})
},
error
:
err
=>
this
.
notifier
.
error
(
err
.
message
),
error
:
err
=>
this
.
notifier
.
error
(
err
.
message
),
complete
:
()
=>
this
.
loading
=
false
})
complete
:
()
=>
this
.
loading
=
false
})
}
isAuditLog
()
{
...
...
client/src/app/+admin/system/logs/logs.service.ts
View file @
64553e88
...
...
@@ -2,7 +2,7 @@ import { Observable } from 'rxjs'
import
{
catchError
,
map
}
from
'
rxjs/operators
'
import
{
HttpClient
,
HttpParams
}
from
'
@angular/common/http
'
import
{
Injectable
}
from
'
@angular/core
'
import
{
RestExtractor
}
from
'
@app/core
'
import
{
RestExtractor
,
RestService
}
from
'
@app/core
'
import
{
LogLevel
}
from
'
@shared/models
'
import
{
environment
}
from
'
../../../../environments/environment
'
import
{
LogRow
}
from
'
./log-row.model
'
...
...
@@ -14,22 +14,25 @@ export class LogsService {
constructor
(
private
authHttp
:
HttpClient
,
private
restService
:
RestService
,
private
restExtractor
:
RestExtractor
)
{}
getLogs
(
options
:
{
isAuditLog
:
boolean
startDate
:
string
tagsOneOf
?:
string
[]
level
?:
LogLevel
endDate
?:
string
}):
Observable
<
any
[]
>
{
const
{
isAuditLog
,
startDate
}
=
options
const
{
isAuditLog
,
startDate
,
endDate
,
tagsOneOf
}
=
options
let
params
=
new
HttpParams
()
params
=
params
.
append
(
'
startDate
'
,
startDate
)
if
(
!
isAuditLog
)
params
=
params
.
append
(
'
level
'
,
options
.
level
)
if
(
options
.
endDate
)
params
.
append
(
'
endDate
'
,
options
.
endDate
)
if
(
endDate
)
params
=
params
.
append
(
'
endDate
'
,
options
.
endDate
)
if
(
tagsOneOf
)
params
=
this
.
restService
.
addArrayParams
(
params
,
'
tagsOneOf
'
,
tagsOneOf
)
const
path
=
isAuditLog
?
LogsService
.
BASE_AUDIT_LOG_URL
...
...
client/src/app/shared/shared-forms/select/select-tags.component.html
View file @
64553e88
...
...
@@ -2,7 +2,7 @@
[items]=
"availableItems"
[(ngModel)]=
"selectedItems"
(ngModelChange)=
"onModelChange()"
i18n-
placeholder
placeholder
=
"Enter a new tag
"
[
placeholder
]=
"
placeholder"
[maxSelectedItems]=
"5"
[clearable]=
"true"
[addTag]=
"true"
...
...
client/src/app/shared/shared-forms/select/select-tags.component.ts
View file @
64553e88
...
...
@@ -16,6 +16,7 @@ import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'
export
class
SelectTagsComponent
implements
ControlValueAccessor
{
@
Input
()
availableItems
:
string
[]
=
[]
@
Input
()
selectedItems
:
string
[]
=
[]
@
Input
()
placeholder
=
$localize
`Enter a new tag`
propagateChange
=
(
_
:
any
)
=>
{
/* empty */
}
...
...
server/controllers/api/server/logs.ts
View file @
64553e88
import
express
from
'
express
'
import
{
readdir
,
readFile
}
from
'
fs-extra
'
import
{
join
}
from
'
path
'
import
{
isArray
}
from
'
@server/helpers/custom-validators/misc
'
import
{
logger
,
mtimeSortFilesDesc
}
from
'
@server/helpers/logger
'
import
{
LogLevel
}
from
'
../../../../shared/models/server/log-level.type
'
import
{
UserRight
}
from
'
../../../../shared/models/users
'
...
...
@@ -51,20 +52,27 @@ async function getLogs (req: express.Request, res: express.Response) {
startDateQuery
:
req
.
query
.
startDate
,
endDateQuery
:
req
.
query
.
endDate
,
level
:
req
.
query
.
level
||
'
info
'
,
tagsOneOf
:
req
.
query
.
tagsOneOf
,
nameFilter
:
logNameFilter
})
return
res
.
json
(
output
)
.
end
()
return
res
.
json
(
output
)
}
async
function
generateOutput
(
options
:
{
startDateQuery
:
string
endDateQuery
?:
string
level
:
LogLevel
nameFilter
:
RegExp
tagsOneOf
?:
string
[]
})
{
const
{
startDateQuery
,
level
,
nameFilter
}
=
options
const
tagsOneOf
=
Array
.
isArray
(
options
.
tagsOneOf
)
&&
options
.
tagsOneOf
.
length
!==
0
?
new
Set
(
options
.
tagsOneOf
)
:
undefined
const
logFiles
=
await
readdir
(
CONFIG
.
STORAGE
.
LOG_DIR
)
const
sortedLogFiles
=
await
mtimeSortFilesDesc
(
logFiles
,
CONFIG
.
STORAGE
.
LOG_DIR
)
let
currentSize
=
0
...
...
@@ -80,7 +88,7 @@ async function generateOutput (options: {
const
path
=
join
(
CONFIG
.
STORAGE
.
LOG_DIR
,
meta
.
file
)
logger
.
debug
(
'
Opening %s to fetch logs.
'
,
path
)
const
result
=
await
getOutputFromFile
(
path
,
startDate
,
endDate
,
level
,
currentSize
)
const
result
=
await
getOutputFromFile
(
{
path
,
startDate
,
endDate
,
level
,
currentSize
,
tagsOneOf
}
)
if
(
!
result
.
output
)
break
output
=
result
.
output
.
concat
(
output
)
...
...
@@ -92,9 +100,20 @@ async function generateOutput (options: {
return
output
}
async
function
getOutputFromFile
(
path
:
string
,
startDate
:
Date
,
endDate
:
Date
,
level
:
LogLevel
,
currentSize
:
number
)
{
async
function
getOutputFromFile
(
options
:
{
path
:
string
startDate
:
Date
endDate
:
Date
level
:
LogLevel
currentSize
:
number
tagsOneOf
:
Set
<
string
>
})
{
const
{
path
,
startDate
,
endDate
,
level
,
tagsOneOf
}
=
options
const
startTime
=
startDate
.
getTime
()
const
endTime
=
endDate
.
getTime
()
let
currentSize
=
options
.
currentSize
let
logTime
:
number
const
logsLevel
:
{
[
id
in
LogLevel
]:
number
}
=
{
...
...
@@ -121,7 +140,12 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date,
}
logTime
=
new
Date
(
log
.
timestamp
).
getTime
()
if
(
logTime
>=
startTime
&&
logTime
<=
endTime
&&
logsLevel
[
log
.
level
]
>=
logsLevel
[
level
])
{
if
(
logTime
>=
startTime
&&
logTime
<=
endTime
&&
logsLevel
[
log
.
level
]
>=
logsLevel
[
level
]
&&
(
!
tagsOneOf
||
lineHasTag
(
log
,
tagsOneOf
))
)
{
output
.
push
(
log
)
currentSize
+=
line
.
length
...
...
@@ -135,6 +159,16 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date,
return
{
currentSize
,
output
:
output
.
reverse
(),
logTime
}
}
function
lineHasTag
(
line
:
{
tags
?:
string
},
tagsOneOf
:
Set
<
string
>
)
{
if
(
!
isArray
(
line
.
tags
))
return
false
for
(
const
lineTag
of
line
.
tags
)
{
if
(
tagsOneOf
.
has
(
lineTag
))
return
true
}
return
false
}
function
generateLogNameFilter
(
baseName
:
string
)
{
return
new
RegExp
(
'
^
'
+
baseName
.
replace
(
/
\.
log$/
,
''
)
+
'
\\
d*.log$
'
)
}
server/middlewares/validators/logs.ts
View file @
64553e88
import
express
from
'
express
'
import
{
query
}
from
'
express-validator
'
import
{
isStringArray
}
from
'
@server/helpers/custom-validators/search
'
import
{
isValidLogLevel
}
from
'
../../helpers/custom-validators/logs
'
import
{
isDateValid
}
from
'
../../helpers/custom-validators/misc
'
import
{
isDateValid
,
toArray
}
from
'
../../helpers/custom-validators/misc
'
import
{
logger
}
from
'
../../helpers/logger
'
import
{
areValidationErrors
}
from
'
./shared
'
...
...
@@ -11,6 +12,10 @@ const getLogsValidator = [
query
(
'
level
'
)
.
optional
()
.
custom
(
isValidLogLevel
).
withMessage
(
'
Should have a valid level
'
),
query
(
'
tagsOneOf
'
)
.
optional
()
.
customSanitizer
(
toArray
)
.
custom
(
isStringArray
).
withMessage
(
'
Should have a valid one of tags array
'
),
query
(
'
endDate
'
)
.
optional
()
.
custom
(
isDateValid
).
withMessage
(
'
Should have an end date that conforms to ISO 8601
'
),
...
...
server/tests/api/server/logs.ts
View file @
64553e88
...
...
@@ -71,7 +71,7 @@ describe('Test logs', function () {
expect
(
logsString
.
includes
(
'
video 5
'
)).
to
.
be
.
false
})
it
(
'
Should
get
filter by level
'
,
async
function
()
{
it
(
'
Should filter by level
'
,
async
function
()
{
this
.
timeout
(
20000
)
const
now
=
new
Date
()
...
...
@@ -94,6 +94,27 @@ describe('Test logs', function () {
}
})
it
(
'
Should filter by tag
'
,
async
function
()
{
const
now
=
new
Date
()
const
{
uuid
}
=
await
server
.
videos
.
upload
({
attributes
:
{
name
:
'
video 6
'
}
})
await
waitJobs
([
server
])
{
const
body
=
await
logsCommand
.
getLogs
({
startDate
:
now
,
level
:
'
debug
'
,
tagsOneOf
:
[
'
toto
'
]
})
expect
(
body
).
to
.
have
.
lengthOf
(
0
)
}
{
const
body
=
await
logsCommand
.
getLogs
({
startDate
:
now
,
level
:
'
debug
'
,
tagsOneOf
:
[
uuid
]
})
expect
(
body
).
to
.
not
.
have
.
lengthOf
(
0
)
for
(
const
line
of
body
)
{
expect
(
line
.
tags
).
to
.
contain
(
uuid
)
}
}
})
it
(
'
Should log ping requests
'
,
async
function
()
{
this
.
timeout
(
10000
)
...
...
shared/extra-utils/logs/logs-command.ts
View file @
64553e88
...
...
@@ -8,15 +8,16 @@ export class LogsCommand extends AbstractCommand {
startDate
:
Date
endDate
?:
Date
level
?:
LogLevel
tagsOneOf
?:
string
[]
})
{
const
{
startDate
,
endDate
,
level
}
=
options
const
{
startDate
,
endDate
,
tagsOneOf
,
level
}
=
options
const
path
=
'
/api/v1/server/logs
'
return
this
.
getRequestBody
({
return
this
.
getRequestBody
<
any
[]
>
({
...
options
,
path
,
query
:
{
startDate
,
endDate
,
level
},
query
:
{
startDate
,
endDate
,
level
,
tagsOneOf
},
implicitToken
:
true
,
defaultExpectedStatus
:
HttpStatusCode
.
OK_200
})
...
...
Write
Preview
Supports
Markdown
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