Skip to content

Commit

Permalink
#52: Enable to navigate to next or previous log entry directly from the
Browse files Browse the repository at this point in the history
log entry details dialog
  • Loading branch information
mbok committed Feb 3, 2016
1 parent 820c7f4 commit 7fe75d6
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import com.fasterxml.jackson.annotation.JsonRawValue;

/**
* Abstraction of pointing a byte position inside a log.
* Abstraction of pointing a byte position inside a log. The
* {@link #equals(Object)} method has to be implemented properly to compare
* pointers.
*
* @author mbok
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,13 @@ public class LogEntriesResult {

private List<LogEntry> entries;

public LogEntriesResult(final LinkedHashMap<String, FieldBaseTypes> fieldTypes,
final List<LogEntry> entries) {
private int highlightEntry;

public LogEntriesResult(final LinkedHashMap<String, FieldBaseTypes> fieldTypes, final List<LogEntry> entries,
int highlightEntry) {
this.fieldTypes = fieldTypes;
this.entries = entries;
this.highlightEntry = highlightEntry;
}

public LogEntriesResult() {
Expand Down Expand Up @@ -74,4 +77,12 @@ public void setEntries(final List<LogEntry> entries) {
this.entries = entries;
}

public int getHighlightEntry() {
return highlightEntry;
}

public void setHighlightEntry(int highlightEntry) {
this.highlightEntry = highlightEntry;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,35 @@ private LogSource<LogInputStream> getActiveLogSource(final long logSourceId) thr
}
}

private int getHighlightEntryIndex(LogPointer desiredPointer, int desiredCount, List<LogEntry> entries) {
if (!entries.isEmpty()) {
if (desiredCount >= 0) {
if (desiredPointer == null) {
return 0;
} else {
for (int i = 0; i < entries.size(); i++) {
if (desiredPointer.equals(entries.get(i).getStartOffset())) {
return i;
}
}
}

} else {
if (desiredPointer == null) {
return entries.size() - 1;
} else {
for (int i = entries.size() - 1; i >= 0; i--) {
if (desiredPointer.equals(entries.get(i).getEndOffset())) {
return i;
}
}

}
}
}
return -1;
}

@SuppressWarnings({ "rawtypes", "unchecked" })
@RequestMapping(value = "/sources/entries", method = RequestMethod.POST)
@ResponseBody
Expand All @@ -106,10 +135,13 @@ LogEntriesResult getEntries(@Valid @RequestBody final LogSource<LogInputStream>
if (count > 0) {
final BufferedConsumer bc = new BufferedConsumer(count);
activeLogSource.getReader().readEntries(log, logAccess, pointer, bc);
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), bc.getBuffer());
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), bc.getBuffer(),
getHighlightEntryIndex(pointer, count, bc.getBuffer()));
} else {
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(),
new BackwardReader(activeLogSource.getReader()).readEntries(log, logAccess, pointer, count));
List<LogEntry> readEntries = new BackwardReader(activeLogSource.getReader()).readEntries(log, logAccess,
pointer, count);
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), readEntries,
getHighlightEntryIndex(pointer, count, readEntries));
}

} finally {
Expand Down Expand Up @@ -154,8 +186,10 @@ LogEntriesResult getRandomAccessEntries(@Valid @RequestBody final LogSource<LogI
pointer = logAccess.createRelative(pointer, 0);
if (pointer.isEOF()) {
// End pointer, return the last 10 simply
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(),
new BackwardReader(activeLogSource.getReader()).readEntries(log, logAccess, pointer, -10));
List<LogEntry> readEntries = new BackwardReader(activeLogSource.getReader()).readEntries(log,
logAccess, pointer, -10);
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), readEntries,
getHighlightEntryIndex(pointer, -10, readEntries));
}
}
activeLogSource.getReader().readEntries(log, logAccess,
Expand All @@ -166,19 +200,20 @@ LogEntriesResult getRandomAccessEntries(@Valid @RequestBody final LogSource<LogI
final LogEntry last = entries.get(entries.size() - 1);
if (!first.getStartOffset().isSOF() && last.getEndOffset().isEOF() && entries.size() < 10) {
// Hm, EOF reached
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(),
new BackwardReader(activeLogSource.getReader()).readEntries(log, logAccess,
last.getEndOffset(), -10));
List<LogEntry> readEntries = new BackwardReader(activeLogSource.getReader()).readEntries(log,
logAccess, last.getEndOffset(), -10);
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), readEntries, -1);
} else if (logAccess.getDifference(pointer, first.getStartOffset()) == 0) {
// -1 without effect, return from beginning
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), entries);
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), entries,
getHighlightEntryIndex(pointer, 0, entries));
} else {
// Return from the second one
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(),
entries.subList(1, entries.size()));
entries.subList(1, entries.size()), getHighlightEntryIndex(pointer, 0, entries) - 1);
}
} else {
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), entries);
return new LogEntriesResult(activeLogSource.getReader().getFieldTypes(), entries, -1);
}
} finally {
logger.debug("Finished loading random access entries from log={} and source={}", logPath, activeLogSource);
Expand Down Expand Up @@ -299,7 +334,7 @@ public void consume(final Event eventData) throws IOException {
logger.debug("Found next entry of interest in {} at: {}", log, searchResult.lastPointer);
final BufferedConsumer bc = new BufferedConsumer(Math.abs(count));
source.getReader().readEntries(log, logAccess, searchResult.lastPointer, bc);
searchResult.entries = new LogEntriesResult(source.getReader().getFieldTypes(), bc.getBuffer());
searchResult.entries = new LogEntriesResult(source.getReader().getFieldTypes(), bc.getBuffer(), 0);
} else if (searchResult.sofReached) {
// Return start pointer
searchResult.lastPointer = logAccess.createRelative(null, 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,19 +97,15 @@ public void setTestLogData(final String testLogData) {
LogEntriesResult testReading(@RequestBody @Valid final TestContext testCtx)
throws UnsupportedEncodingException, IOException, FormatException {
final ArrayList<LogEntry> entries = new ArrayList<LogEntry>();
ByteArrayLog tempLog = new ByteArrayLog(testCtx.getTestLogData()
.getBytes("UTF-8"));
testCtx.getReader().readEntries(tempLog, tempLog, null,
new LogEntryConsumer() {
@Override
public boolean consume(final Log log,
final LogPointerFactory pointerFactory,
final LogEntry entry) throws IOException {
entries.add(entry);
return true;
}
});
return new LogEntriesResult(testCtx.getReader().getFieldTypes(),
entries);
ByteArrayLog tempLog = new ByteArrayLog(testCtx.getTestLogData().getBytes("UTF-8"));
testCtx.getReader().readEntries(tempLog, tempLog, null, new LogEntryConsumer() {
@Override
public boolean consume(final Log log, final LogPointerFactory pointerFactory, final LogEntry entry)
throws IOException {
entries.add(entry);
return true;
}
});
return new LogEntriesResult(testCtx.getReader().getFieldTypes(), entries, -1);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
$log.warn("Failed to init log viewer with erroneous JSON pointer: ", pointerParam, e);
}
}
$scope.highlightPointer = $location.search().highlight=="true";
$scope.$on("viewerFieldsChanged", function(event, viewerFields) {
$log.info("Saving changed viewer fields as profile settings", viewerFields);
Expand Down Expand Up @@ -105,7 +106,7 @@

<lsf-log-viewer source="source" log="log" pointer="pointer" fix-top-element-selector=".navbar-fixed-top"
init-tail="initTail" search-wizards="scannerWizards" full-height="true" configured-viewer-fields="viewerFields"
default-viewer-fields="source.uiSettings.viewerFields" viewer-fields-config-enabled="true"></lsf-log-viewer>
default-viewer-fields="source.uiSettings.viewerFields" viewer-fields-config-enabled="true" highlight-pointer="highlightPointer"></lsf-log-viewer>

</div>
</jsp:body>
Expand Down
4 changes: 2 additions & 2 deletions logsniffer-web/src/main/webapp/ng/entry/zoomEntry.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ <h4 class="modal-title">Log entry details</h4>
<i class="fa fa-link"></i> Permalink <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="single-button">
<li role="menuitem"><a href ngclipboard data-clipboard-text="{{absEntryViewerLink}}"><i class="fa fa-clipboard"></i> Copy to clipboard</a></li>
<li role="menuitem"><a href="{{entryViewerLink}}" target="_blank"><i class="fa fa-external-link"></i> Open in new window</a></li>
<li role="menuitem"><a href ngclipboard data-clipboard-text="{{absEntryViewerLink}}"><i class="fa fa-clipboard"></i> Copy log entry link to clipboard</a></li>
<li role="menuitem"><a href="{{entryViewerLink}}" target="_blank"><i class="fa fa-external-link"></i> Open log entry link in new window</a></li>
</ul>
</div>
<button class="btn btn-primary" ng-click="close()">Close</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ <h4>Log entries</h4>
<div class="clearfix">
<div class="pull-left" style="position:absolute">
<a class="btn btn-default btn-xs" href="#" ng-click="zoomEntry({entry:entry})" onclick="return false"><i class="glyphicon glyphicon-zoom-in"></i> Show entry details</a>
<a class="btn btn-default btn-xs" href="{{contextPath}}/c/sources/{{event.lf_logSourceId}}/show?log={{event.lf_logPath | escape}}#?pointer={{entry.lf_startOffset.json | json:0 | escape}}"><i class="glyphicon glyphicon-list"></i> Open in log</a>
<a class="btn btn-default btn-xs" href="{{contextPath}}/c/sources/{{event.lf_logSourceId}}/show?log={{event.lf_logPath | escape}}#?highlight=true&pointer={{entry.lf_startOffset.json | json:0 | escape}}"><i class="glyphicon glyphicon-list"></i> Open in log</a>
</div>
<div class="pull-right text-muted">
{{event.lf_logPath}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
<div class="pull-left hidden2" style="position:absolute">
<a class="btn btn-default btn-xs" href="#/{{event._id}}"><i class="glyphicon glyphicon-bullhorn"></i> Open event</a>
<a class="btn btn-default btn-xs" ng-if="event.lf_entries && event.lf_entries[0] && event.lf_entries[0].lf_startOffset"
href="{{contextPath}}/c/sources/{{event.lf_logSourceId}}/show?log={{event.lf_logPath | escape}}#?pointer={{event.lf_entries[0].lf_startOffset.json | json:0 | escape}}"><i class="glyphicon glyphicon-list"></i> Open in log</a>
href="{{contextPath}}/c/sources/{{event.lf_logSourceId}}/show?log={{event.lf_logPath | escape}}#?highlight=true&pointer={{event.lf_entries[0].lf_startOffset.json | json:0 | escape}}"><i class="glyphicon glyphicon-list"></i> Open in log</a>
<!-- <a class="btn btn-default btn-xs" href="#"><i class="glyphicon glyphicon-bookmark"></i> Bookmark</a>
<a class="btn btn-default btn-xs" href="#"><i class="glyphicon glyphicon-eye-open"></i> Mark as read</a>
<a class="btn btn-default btn-xs" href ng-click="deleteEvent($index, event)"><i class="glyphicon glyphicon-trash"></i> Delete event</a>
Expand Down
5 changes: 5 additions & 0 deletions logsniffer-web/src/main/webapp/static/logsniffer.css
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ ul.bordered-list li {
border-color: #eed3d7;
}

.log-viewer tr.selected {
background-color: #d9edf7;
font-weight: bold;
}

/** Misc **/
.busy-container {
position: relative;
Expand Down
2 changes: 1 addition & 1 deletion logsniffer-web/src/main/webapp/static/logsniffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ LogPosition.prototype.resetToEnd = function() {
+ getSeverityClass(e.lf_severity) + '" sof=\''
+ e.lf_endOffset.sof + '\' start=\''
+ JSON.stringify(e.lf_startOffset.json) + '\' eof=\'' + e.lf_endOffset.eof
+ '\' end=\'' + JSON.stringify(e.lf_endOffset.json) + '\'>';
+ '\' end=\'' + JSON.stringify(e.lf_endOffset.json) + '\' onclick="$(this).toggleClass(\'selected\')">';
if (cellsBeforeCallback) {
html += cellsBeforeCallback(fieldsTypes, e);
}
Expand Down
38 changes: 26 additions & 12 deletions logsniffer-web/src/main/webapp/static/logsniffer.ng-core.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
searchExpanded: '&',
onError: '&',
fullHeight: '@',
highlightPointer: '&'
},
controller: function($scope) {
$scope.searchSettings= {
Expand Down Expand Up @@ -401,7 +402,7 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
$scope.searchStatus="hit";
$('#search-progress-modal').modal("hide");
entriesUpdCallback(data.entries.entries);
$("#log-entries-frame tbody tr:eq(0)").addClass("info");
$("#log-entries-frame tbody tr:eq(0)").addClass("selected");
if (document.location.hash!="search-control") {
document.location.hash="search-control";
}
Expand Down Expand Up @@ -653,8 +654,14 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
});
return loaderPromise.promise;
}
if (currentRow.hasClass("fromzoom") && currentRow.hasClass("selected")) {
currentRow.removeClass("selected").removeClass("fromzoom");
}
$log.debug("Next id to switch to", nextId);
if (nextId && logEntries[nextId]) {
if (!nextRow.hasClass("selected")) {
nextRow.addClass("selected").addClass("fromzoom");
}
$(nextRow).scrollintoview();
loaderContext.id = nextId;
loaderPromise.resolve(logEntries[nextId]);
Expand All @@ -671,10 +678,14 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
});
};
function renderPrefixCells(fieldsTypes, e) {
entriesStaticCounter++;
var id = 'entry-' + entriesStaticCounter;
logEntries[id] = e;
return '<td class="zoom"><a href="#" class="zoom" title="Open entry details" onclick="angular.element(this).scope().zoomViewerEntry(this.id)" id="'+id+'"><i class="glyphicon glyphicon-zoom-in"/></a></td>';
entriesStaticCounter++;
var id = 'entry-' + entriesStaticCounter;
logEntries[id] = e;
var entryViewerLink = null;
if (scope.source.id && scope.log.path && e.lf_startOffset) {
entryViewerLink = LogSniffer.config.contextPath + '/c/sources/'+ scope.source.id +'/show?log=' + encodeURIComponent(scope.log.path) +'#?highlight=true&pointer='+ encodeURIComponent(JSON.stringify(e.lf_startOffset.json));
}
return '<td class="zoom"><a href'+(entryViewerLink?'="'+entryViewerLink+'"':'')+' class="zoom" title="Open entry details" onclick="angular.element(this).scope().zoomViewerEntry(this.id);event.stopPropagation();return false" id="'+id+'"><i class="glyphicon glyphicon-zoom-in"/></a></td>';
}

function adaptSlidingEntriesWindow(cutHead, adaptScroll) {
Expand Down Expand Up @@ -705,7 +716,7 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
}
};

function loadRandomAccessEntries(jsonMark) {
function loadRandomAccessEntries(jsonMark, highlight) {
scope.disableTailFollow();
var entriesUpdCallback = scope.getEntriesUpdateCallback();
var pStr = "";
Expand All @@ -730,7 +741,7 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
entriesCall.success(function(data, status, headers, config) {
emptyViewerEntries();
renderTableHead(data.fieldTypes);
entriesUpdCallback(data.entries);
entriesUpdCallback(data.entries, highlight ? data.highlightEntry : -1);
}).error(function(data, status, headers, config, statusText) {
entriesUpdCallback(null);
scope.handleHttpError("Failed to load entries", data, status, headers, config, statusText);
Expand Down Expand Up @@ -782,11 +793,14 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
scope.getEntriesUpdateCallback = function() {
loadingEntries=true;
var spinner=new Spinner().spin($(element).find("#log-entries-frame")[0]);
return function(entries) {
return function(entries, highlightIndex) {
if (entries != null) {
emptyViewerEntries();
$(element).find('.log-entries tbody').empty();
$(element).find('.log-entries tbody').append($.LogSniffer.entriesRows(scope.fieldTypes, scope.viewerFields, entries, renderPrefixCells));
if (highlightIndex >= 0) {
$(element).find('.log-entries tbody tr:eq('+highlightIndex+')').addClass("selected");
}
$(element).find('#log-entries-frame').scrollTop(10);
if (entries.length>0) {
scope.setPointer(entries[0].lf_startOffset.json);
Expand Down Expand Up @@ -814,9 +828,9 @@ angular.module('LogSnifferCore', ['jsonFormatter'])


// Init 1
scope.reset = function() {
scope.reset = function(start) {
if (angular.isObject(scope.mark) && LogSniffer.objSize(scope.mark)>0) {
loadRandomAccessEntries(scope.mark);
loadRandomAccessEntries(scope.mark, start && scope.highlightPointer()===true);
} else {
loadEntries(scope.mark, scope.initTail());
}
Expand All @@ -828,7 +842,7 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
});

// Init 1
scope.reset();
scope.reset(true);

var scrollCallback = function(fireSequence, pageSequence, scrollDirection, loadingSuccessCallback, loadingErrorCallback) {
if (loadingEntries) {
Expand Down Expand Up @@ -1098,7 +1112,7 @@ angular.module('LogSnifferCore', ['jsonFormatter'])
var loaderContext = {};
function updatePermalinks() {
if (context.sourceId && context.logPath && $scope.entry.lf_startOffset && $scope.entry.lf_startOffset.json) {
$scope.entryViewerLink = LogSniffer.config.contextPath + '/c/sources/'+ context.sourceId +'/show?log=' + encodeURIComponent(context.logPath) +'#?zoom=1&pointer='+ encodeURIComponent(JSON.stringify($scope.entry.lf_startOffset.json));
$scope.entryViewerLink = LogSniffer.config.contextPath + '/c/sources/'+ context.sourceId +'/show?log=' + encodeURIComponent(context.logPath) +'#?highlight=true&pointer='+ encodeURIComponent(JSON.stringify($scope.entry.lf_startOffset.json));
var p = window.location.href.split("/");
$scope.absEntryViewerLink = p[0] + "//" + p[2] + $scope.entryViewerLink;
} else {
Expand Down

0 comments on commit 7fe75d6

Please sign in to comment.