gui: Add debug tab to settings (ref #2644)

Just because there are a ton of people struggling to set env vars.
Perhaps this should live in advanced settings, and perhaps we should have a button to view the log.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4604
LGTM: calmh, imsodin
This commit is contained in:
Audrius Butkevicius 2017-12-24 22:26:05 +00:00 committed by Simon Frei
parent 2547a29dd7
commit c58b383b6d
7 changed files with 148 additions and 16 deletions

View File

@ -78,6 +78,7 @@
<li><a href="" data-toggle="modal" data-target="#about"><span class="fa fa-fw fa-heart-o"></span>&nbsp;<span translate>About</span></a></li>
<li class="divider" aria-hidden="true"></li>
<li><a href="" ng-click="advanced()"><span class="fa fa-fw fa-cogs"></span>&nbsp;<span translate>Advanced</span></a></li>
<li><a href="" ng-click="logging.show()"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Logs</span></a></li>
</ul>
</li>
</ul>
@ -734,6 +735,7 @@
<ng-include src="'syncthing/core/discoveryFailuresModalView.html'"></ng-include>
<ng-include src="'syncthing/folder/removeFolderDialogView.html'"></ng-include>
<ng-include src="'syncthing/device/removeDeviceDialogView.html'"></ng-include>
<ng-include src="'syncthing/core/logViewerModalView.html'"></ng-include>
<!-- vendor scripts -->
<script type="text/javascript" src="vendor/jquery/jquery-2.2.2.js"></script>

View File

@ -0,0 +1,36 @@
<modal id="logViewer" icon="book" status="default" heading="{{'Logs' | translate}}" large="yes" closeable="yes">
<div class="modal-body">
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#log-viewer-log" translate>Log</a></li>
<li><a data-toggle="tab" href="#log-viewer-facilities" translate>Debugging Facilities</a></li>
</ul>
<div class="tab-content">
<div id="log-viewer-log" class="tab-pane in active">
<label translate ng-if="logging.logEntries.length == 0">Loading...</label>
<textarea ng-focus="logging.paused = true" ng-blur="logging.paused = false" id="logViewerText" class="form-control" rows="20" ng-if="logging.logEntries.length != 0" readonly style="font-family: Consolas; font-size: 11px; overflow: auto;">{{ logging.content() }}</textarea>
<p translate class="help-block" ng-style="{'visibility': logging.paused ? 'visible' : 'hidden'}">Log tailing paused. Click here to continue.</p>
</div>
<div id="log-viewer-facilities" class="tab-pane">
<label>Available debug logging facilities:</label>
<table class="table table-condensed table-striped">
<tbody>
<tr ng-repeat="(name, data) in logging.facilities">
<td>
<input type="checkbox" ng-model="data.enabled" ng-change="logging.onFacilityChange(name)" ng-disabled="data.enabled == null"> <span>{{ name }}</span>
</td>
<td>{{ data.description }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default btn-sm" data-dismiss="modal">
<span class="fa fa-times"></span>&nbsp;<span translate>Close</span>
</button>
</div>
</modal>

View File

@ -2,7 +2,7 @@ angular.module('syncthing.core')
.config(function($locationProvider) {
$locationProvider.html5Mode({enabled: true, requireBase: false}).hashPrefix('!');
})
.controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter, $q) {
.controller('SyncthingController', function ($scope, $http, $location, LocaleService, Events, $filter, $q, $interval) {
'use strict';
// private/helper definitions
@ -1090,6 +1090,76 @@ angular.module('syncthing.core')
$('#discovery-failures').modal();
};
$scope.logging = {
facilities: {},
refreshFacilities: function() {
$http.get(urlbase + '/system/debug').success(function (data) {
var facilities = {};
data.enabled = data.enabled || [];
$.each(data.facilities, function(key, value) {
facilities[key] = {
description: value,
enabled: data.enabled.indexOf(key) > -1
}
})
$scope.logging.facilities = facilities;
}).error($scope.emitHTTPError);
},
show: function() {
$scope.logging.refreshFacilities();
$scope.logging.timer = $interval($scope.logging.fetch, 0, 1);
$('#logViewer').modal().on('hidden.bs.modal', function () {
$interval.cancel($scope.logging.timer);
$scope.logging.timer = null;
$scope.logging.entries = [];
});
},
onFacilityChange: function(facility) {
var enabled = $scope.logging.facilities[facility].enabled;
// Disable checkboxes while we're in flight.
$.each($scope.logging.facilities, function(key) {
$scope.logging.facilities[key].enabled = null;
})
$http.post(urlbase + '/system/debug?' + (enabled ? 'enable=':'disable=') + facility)
.success($scope.logging.refreshFacilities)
.error($scope.emitHTTPError);
},
timer: null,
entries: [],
paused: false,
content: function() {
var content = "";
$.each($scope.logging.entries, function (idx, entry) {
content += entry.when.split('.')[0].replace('T', ' ') + ' ' + entry.message + "\n";
});
return content;
},
fetch: function() {
var textArea = $('#logViewerText');
if (textArea.is(":focus")) {
if (!$scope.logging.timer) return;
$scope.logging.timer = $interval($scope.logging.fetch, 500, 1);
return;
}
var last = null;
if ($scope.logging.entries.length > 0) {
last = $scope.logging.entries[$scope.logging.entries.length-1].when;
}
$http.get(urlbase + '/system/log' + (last ? '?since=' + encodeURIComponent(last) : '')).success(function (data) {
if (!$scope.logging.timer) return;
$scope.logging.timer = $interval($scope.logging.fetch, 2000, 1);
if (!textArea.is(":focus")) {
if (data.messages) {
$scope.logging.entries.push.apply($scope.logging.entries, data.messages);
}
textArea.scrollTop(textArea[0].scrollHeight);
}
});
}
}
$scope.editSettings = function () {
// Make a working copy
$scope.tmpOptions = angular.copy($scope.config.options);

View File

@ -150,6 +150,7 @@
</div>
</div>
</div>
<div id="settings-connections" class="tab-pane">
<div class="form-group">
<label translate for="ListenAddressesStr">Sync Protocol Listen Addresses</label>&emsp;<a href="https://docs.syncthing.net/users/config.html#listen-addresses" target="_blank"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span></a>
@ -221,11 +222,12 @@
<label translate for="GlobalAnnServersStr">Global Discovery Servers</label>
<input ng-disabled="!tmpOptions.globalAnnounceEnabled" id="GlobalAnnServersStr" class="form-control" type="text" ng-model="tmpOptions._globalAnnounceServersStr"/>
</div>
<div>
</div>
<div class="col-md-6">
</div>
</div>
</div>
</div>
</form>
</div>

View File

@ -14,9 +14,9 @@ import (
)
var (
l = logger.DefaultLogger.NewFacility("filesystem", "Filesystem access")
l = logger.DefaultLogger.NewFacility("fs", "Filesystem access")
)
func init() {
l.SetDebug("filesystem", strings.Contains(os.Getenv("STTRACE"), "filesystem") || os.Getenv("STTRACE") == "all")
l.SetDebug("fs", strings.Contains(os.Getenv("STTRACE"), "fs") || os.Getenv("STTRACE") == "all")
}

View File

@ -308,6 +308,7 @@ type recorder struct {
type Line struct {
When time.Time `json:"when"`
Message string `json:"message"`
Level LogLevel `json:"level"`
}
func NewRecorder(l Logger, level LogLevel, size, initial int) Recorder {
@ -324,18 +325,18 @@ func (r *recorder) Since(t time.Time) []Line {
defer r.mut.Unlock()
res := r.lines
for i := 0; i < len(res) && res[i].When.Before(t); i++ {
// nothing, just incrementing i
}
if len(res) == 0 {
return nil
}
// We must copy the result as r.lines can be mutated as soon as the lock
// is released.
cp := make([]Line, len(res))
copy(cp, res)
return cp
for i := 0; i < len(res); i++ {
if res[i].When.After(t) {
// We must copy the result as r.lines can be mutated as soon as the lock
// is released.
res = res[i:]
cp := make([]Line, len(res))
copy(cp, res)
return cp
}
}
return nil
}
func (r *recorder) Clear() {
@ -348,6 +349,7 @@ func (r *recorder) append(l LogLevel, msg string) {
line := Line{
When: time.Now(),
Message: msg,
Level: l,
}
r.mut.Lock()
@ -367,6 +369,6 @@ func (r *recorder) append(l LogLevel, msg string) {
r.lines = append(r.lines, line)
if len(r.lines) == r.initial {
r.lines = append(r.lines, Line{time.Now(), "..."})
r.lines = append(r.lines, Line{time.Now(), "...", l})
}
}

View File

@ -130,4 +130,24 @@ func TestRecorder(t *testing.T) {
}
}
// Check that since works
now := time.Now()
time.Sleep(time.Millisecond)
lines = r1.Since(now)
if len(lines) != 0 {
t.Error("unexpected lines")
}
l.Infoln("hah")
lines = r1.Since(now)
if len(lines) != 1 {
t.Fatalf("unexpected line count: %d", len(lines))
}
if lines[0].Message != "hah" {
t.Errorf("incorrect line: %s", lines[0])
}
}