Skip to content

Commit

Permalink
Upgrade logging (#18)
Browse files Browse the repository at this point in the history
* Updated DRES results logging to include stage index for staged queries.

* Fixed bug logging object as segment query results.

* Refactored results logging from DresClientManager to a new logging class separating file and DRES logging.

* Refactored submission logging and fixed fast file logging through semaphores.

* Refactored interaction logging.

Moved interaction logging functions to LoggingController.
Removed all file logging from DresClientManager.

* Implemented more fine grained custom interaction logging category enum.

* Implemented logging to file of source calling class.
  • Loading branch information
Spiess authored Dec 7, 2022
1 parent b8bc3f8 commit fa51711
Show file tree
Hide file tree
Showing 15 changed files with 446 additions and 217 deletions.
28 changes: 12 additions & 16 deletions Assets/Scripts/VitrivrVR/Input/Text/SceneTextInputController.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using UnityEngine;
using VitrivrVR.Config;
using Dev.Dres.ClientApi.Model;
using VitrivrVR.Submission;
using VitrivrVR.Logging;

namespace VitrivrVR.Input.Text
{
Expand All @@ -13,45 +11,43 @@ public class SceneTextInputController : MonoBehaviour
public void InputText(string text)
{
TextInputManager.InputText(text);
DresClientManager.LogInteraction("keyboard", $"input {text}", QueryEvent.CategoryEnum.TEXT);
LoggingController.LogInteraction("keyboard", $"input {text}", Logging.Interaction.TextInput);
}

public void InputBackspace()
{
TextInputManager.InputBackspace();
DresClientManager.LogInteraction("keyboard", "backspace", QueryEvent.CategoryEnum.TEXT);
LoggingController.LogInteraction("keyboard", "backspace", Logging.Interaction.TextInput);
}

public void InputReturn()
{
TextInputManager.InputReturn();
DresClientManager.LogInteraction("keyboard", "return", QueryEvent.CategoryEnum.TEXT);
LoggingController.LogInteraction("keyboard", "return", Logging.Interaction.TextInput);
}

public void InputLeftArrow()
{
TextInputManager.InputLeftArrow();
DresClientManager.LogInteraction("keyboard", "ArrowLeft", QueryEvent.CategoryEnum.TEXT);
LoggingController.LogInteraction("keyboard", "ArrowLeft", Logging.Interaction.TextInput);
}

public void InputRightArrow()
{
TextInputManager.InputRightArrow();
DresClientManager.LogInteraction("keyboard", "ArrowRight", QueryEvent.CategoryEnum.TEXT);
LoggingController.LogInteraction("keyboard", "ArrowRight", Logging.Interaction.TextInput);
}

public void InputTabulator()
{
TextInputManager.InputTabulator();
DresClientManager.LogInteraction("keyboard", "Tabulator", QueryEvent.CategoryEnum.TEXT);
LoggingController.LogInteraction("keyboard", "Tabulator", Logging.Interaction.TextInput);
}

public void ReceiveDictationResult(string text)
{
InputText(text);
if (ConfigManager.Config.dresEnabled)
{
DresClientManager.LogInteraction("speechToText", $"input {text} DeepSpeech", QueryEvent.CategoryEnum.TEXT);
}
LoggingController.LogInteraction("speechToText", $"input {text} DeepSpeech", Logging.Interaction.TextInput);
}
}
}
8 changes: 8 additions & 0 deletions Assets/Scripts/VitrivrVR/Logging.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions Assets/Scripts/VitrivrVR/Logging/Interaction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
namespace VitrivrVR.Logging
{
public enum Interaction
{
TextInput,
QueryFormulation,
Query,
QueryManagement,
Browsing,
Filter,
ResultExpansion,
Other
}
}
3 changes: 3 additions & 0 deletions Assets/Scripts/VitrivrVR/Logging/Interaction.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

259 changes: 259 additions & 0 deletions Assets/Scripts/VitrivrVR/Logging/LoggingController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;
using Org.Vitrivr.CineastApi.Model;
using Vitrivr.UnityInterface.CineastApi.Model.Data;
using VitrivrVR.Config;
using VitrivrVR.Notification;
using VitrivrVR.Submission;

namespace VitrivrVR.Logging
{
/// <summary>
/// Static class for all interaction and usage logs.
///
/// The goal of this class is to separate the log sources from different kinds of loggers, such as file and
/// competition loggers.
/// </summary>
public static class LoggingController
{
private static readonly string StartDate = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
private static readonly string LogDir = Path.Combine(ConfigManager.Config.logFileLocation, StartDate);
private static readonly string ResultLogLocation = Path.Combine(LogDir, "results.txt");
private static readonly string SubmissionLogLocation = Path.Combine(LogDir, "submission.txt");
private static readonly string InteractionLogLocation = Path.Combine(LogDir, "interactions.txt");

private static readonly SemaphoreSlim SubmissionLogLock = new(1, 1);
private static readonly SemaphoreSlim ResultLogLock = new(1, 1);
private static readonly SemaphoreSlim InteractionLogLock = new(1, 1);

private static long CurrentTimestamp => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();

/// <summary>
/// Logs ranked results lists for similarity and staged similarity queries.
/// </summary>
/// <param name="sortOrder">The order in which the ranked results are displayed (e.g. by segment or object).</param>
/// <param name="results">List of ranked results.</param>
/// <param name="queryResponse">Query response containing the source query.</param>
public static void LogQueryResults(string sortOrder, List<ScoredSegment> results, QueryResponse queryResponse)
{
var timestamp = CurrentTimestamp;

// Log to DRES
if (ConfigManager.Config.dresEnabled)
{
// Similarity query
if (queryResponse.Query != null)
{
DresClientManager.LogResults(timestamp, sortOrder, results, queryResponse.Query);
}

// Staged query
if (queryResponse.StagedQuery != null)
{
DresClientManager.LogResults(timestamp, sortOrder, results, queryResponse.StagedQuery);
}
}

// Log to file
if (ConfigManager.Config.writeLogsToFile)
{
LogQueryResultsToFile(timestamp, sortOrder, results, queryResponse);
}
}

/// <summary>
/// Logs ranked results lists for temporal similarity queries.
/// </summary>
public static void LogQueryResults(string sortOrder, List<TemporalObject> results,
TemporalQueryResponse queryResponse)
{
var timestamp = CurrentTimestamp;

// Log to DRES
if (ConfigManager.Config.dresEnabled)
{
DresClientManager.LogResults(timestamp, sortOrder, results, queryResponse.Query);
}

// Log to file
if (ConfigManager.Config.writeLogsToFile)
{
LogQueryResultsToFile(timestamp, sortOrder, results, queryResponse);
}
}

public static void LogSubmission(string mediaObjectId, int? frame)
{
var timestamp = CurrentTimestamp;
// Log to file
if (ConfigManager.Config.writeLogsToFile)
{
LogSubmissionToFile(timestamp, mediaObjectId, frame);
}
}

public static void LogInteraction(string type, string value, Interaction category)
{
var timestamp = CurrentTimestamp;
var source = new StackTrace().GetFrame(1).GetMethod().ReflectedType?.Name;

// Log to DRES
if (ConfigManager.Config.dresEnabled)
{
DresClientManager.LogInteraction(timestamp, type, value, category);
}

// Log to file
if (ConfigManager.Config.writeLogsToFile)
{
LogInteractionToFile(timestamp, source, type, value, category.ToString());
}
}

#region FileLogger

private static void EnsureDirectoryExists()
{
Directory.CreateDirectory(LogDir);
}

private static async void LogQueryResultsToFile(long timestamp, string sortOrder,
IEnumerable<ScoredSegment> results,
QueryResponse queryResponse)
{
EnsureDirectoryExists();
await ResultLogLock.WaitAsync();
try
{
await using var file = new StreamWriter(ResultLogLocation, true);
var serializableResults = results.Select(segment => new Dictionary<string, string>
{
{ "id", segment.segment.Id },
{ "score", segment.score.ToString(CultureInfo.InvariantCulture) }
});
var jsonResults = JsonConvert.SerializeObject(serializableResults, Formatting.None);
var jsonQuery = QueryToJson(queryResponse);

var resultLog =
$"{{\"timestamp\":{timestamp},\"sortOrder\":\"{sortOrder}\",\"query\":{jsonQuery},\"results\":{jsonResults}}}";
await file.WriteLineAsync(resultLog);
}
catch (Exception e)
{
NotificationController.NotifyError($"Error logging to file: {e.Message}", e);
}
finally
{
ResultLogLock.Release();
}
}

private static async void LogQueryResultsToFile(long timestamp, string sortOrder, List<TemporalObject> results,
TemporalQueryResponse queryResponse)
{
EnsureDirectoryExists();
await ResultLogLock.WaitAsync();
try
{
await using var file = new StreamWriter(ResultLogLocation, true);
var jsonResults = JsonConvert.SerializeObject(results, Formatting.None);
var jsonQuery = JsonConvert.SerializeObject(queryResponse.Query, Formatting.None);

var resultLog =
$"{{\"timestamp\":{timestamp},\"sortOrder\":\"{sortOrder}\",\"query\":{jsonQuery},\"results\":{jsonResults}}}";
await file.WriteLineAsync(resultLog);
}
catch (Exception e)
{
NotificationController.NotifyError($"Error logging to file: {e.Message}", e);
}
finally
{
ResultLogLock.Release();
}
}

private static string QueryToJson(QueryResponse queryResponse)
{
if (queryResponse.Query != null)
{
return JsonConvert.SerializeObject(queryResponse.Query, Formatting.None);
}

if (queryResponse.StagedQuery != null)
{
return JsonConvert.SerializeObject(queryResponse.StagedQuery, Formatting.None);
}

throw new Exception("Query response contains neither similarity nor staged similarity query.");
}

private static async void LogSubmissionToFile(long timestamp, string mediaObjectId, int? frame)
{
EnsureDirectoryExists();
await SubmissionLogLock.WaitAsync();
try
{
await using var file = new StreamWriter(SubmissionLogLocation, true);
var dict = new Dictionary<string, string>
{
{ "timestamp", timestamp.ToString() },
{ "mediaObjectId", mediaObjectId }
};
if (frame.HasValue)
dict["frame"] = frame.ToString();

var json = JsonConvert.SerializeObject(dict, Formatting.None);

await file.WriteLineAsync(json);
}
catch (Exception e)
{
NotificationController.NotifyError($"Error logging to file: {e.Message}");
}
finally
{
SubmissionLogLock.Release();
}
}

private static async void LogInteractionToFile(long timestamp, string source, string type, string value,
string category)
{
EnsureDirectoryExists();
await InteractionLogLock.WaitAsync();
try
{
await using var file = new StreamWriter(InteractionLogLocation, true);
var dict = new Dictionary<string, string>
{
{ "timestamp", timestamp.ToString() },
{ "source", source },
{ "type", type },
{ "value", value },
{ "category", category }
};

var json = JsonConvert.SerializeObject(dict, Formatting.None);

await file.WriteLineAsync(json);
}
catch (Exception e)
{
NotificationController.NotifyError($"Error logging to file: {e.Message}");
}
finally
{
InteractionLogLock.Release();
}
}

#endregion
}
}
3 changes: 3 additions & 0 deletions Assets/Scripts/VitrivrVR/Logging/LoggingController.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fa51711

Please sign in to comment.