Root > Advanced topics > Implementing offline storage for bug reports

Implementing offline storage for bug reports

Previous pageReturn to chapter overviewNext page   

This article is a continuation of how to deal with send failures article. If you are looking for local bug reports - take a look at bug report file settings.

 


 

You may want to store bug reports to some local offline storage (such as a directory) instead of sending reports. You may send reports from such offline storage via some other means later - external background process, task scheduler, etc.

 

EurekaLog do not offer build-in implementation for such feature, because of many questions:

How should EurekaLog limit size of this storage?
How often should EurekaLog clean it?
What if application is uninstalled: who will clear the storage?
How uninstaller will know about EurekaLog's storage location?
Who and when will resend reports from storage?
Should EurekaLog resend on application's startup?
Should EurekaLog resend when another exception occur?
What if application is non-GUI?
What if startup time is important?
What if next exception never occur?
Should EurekaLog monitor network for availability?
Use task scheduler?
What if application is a DLL or COM object; in other words: it can not run by itself to resend reports?
If you will perform sending from another process - what about firewall? Your application may be allowed in firewall, while some random background process is not.
How EurekaLog may inform user that issue was fixed?

 

You have to answer each question before even starting with writing the code.

 


 

Application

 

You have to save bug report with all additional files and options into some storage. The following code illustrates this by saving files into sub-folders. Options for sending are saved to params.json file. You may use this code as is; you may modify it; or you may write your own code based on this example.

 

uses
  EDLLs,     // for CoCreateGuid
  ESend,     // for TELUniversalSender and RegisterSender
  EModules,  // for CurrentEurekaLogOptions
  ESysInfo,  // for GetFolderAppData
  EJSON,     // for JSONCreate and IJSONValues
  EEncoding, // for TextToFile
  ETypes;    // for TResponse
 
type
  // Create a new "send engine".
  // It will be dummy that will perform no actual sending.
  // Instead - it will save all files attachments to folder (offline storage).
  TSaveToFolder = class(TELUniversalSender)
  public
    function SendMessage: TResponse; override;
  end;
 
// The dummy send function will simply copy bug report files to the specified folder
// Folder is hard-coded inside; but you may specify it via options
function TSaveToFolder.SendMessage: TResponse;
 
  // Copies bug report files to the specified folder
  // Also creates parameters file with send options
  procedure CopyBugReport(const AFolder: String);
 
    // Create parameters file, which will contain list of options and files to send
    procedure CreateSendParams(const AFolder: String);
 
      function CopyCurrentEurekaLogOptionsToJSON: IJSONValues;
      var
        Options: TStringList;
        X: Integer;
      begin
        Result := JSONCreate;
        Options := TStringList.Create;
        try
          // .Options property of current sender engine is 

          // a copy of CurrentEurekaLogOptions
          // See for more info
          Options.Assign(Self.Options);
          for X := 0 to Options.Count - 1 do
            Result[Options.Names[X]] := Options.ValueFromIndex[X];
        finally
          FreeAndNil(Options);
        end;
      end;
 
      function CopyBugReportFilesToJSON: IJSONValues;
      var
        X: Integer;
      begin
        Result := JSONCreate;
        Result['count'] := AttachedFiles.Count;
        for X := 0 to AttachedFiles.Count - 1 do
          Result['file' + IntToStr(X)] := AFolder + ExtractFileName(AttachedFiles[X]);
      end;
 
    var
      JSON: IJSONValues;
    begin
      JSON := JSONCreate;
 
      JSON.Items['files']   := CopyBugReportFilesToJSON;
      JSON.Items['options'] := CopyCurrentEurekaLogOptionsToJSON;
 
      TextToFile(AFolder + 'params.json', JSON.ToString, TEncoding.Unicode);
    end;
 
  var
    Folder: String;
    GUID: TGUID;
    X: Integer;
  begin
    // Create sub-folder for bug report
    if Failed(CoCreateGuid(GUID)) then
      Folder := IncludeTrailingPathDelimiter(AFolder) + 

        ChangeFileExt(ExtractFileName(AttachedFiles[0]), '') + 

        PathDelim
    else
      Folder := IncludeTrailingPathDelimiter(AFolder) + 

        GUIDToString(GUID) + '_' + ChangeFileExt(ExtractFileName(AttachedFiles[0]), '') + 

        PathDelim;
    ForceDirectories(Folder);
 
    // Create parameters file, which will contain list of options and files to send
    CreateSendParams(Folder);
 
    // Copy primary attachment (.elp bug report)
    Win32Check(CopyFile(PChar(AttachedFiles[0]), 

      PChar(Folder + ExtractFileName(AttachedFiles[0])), False));
 
    if AttachedFiles.Count = 1 then
      Exit;
 
    // Copy all remaining files attachments to some folder.
    // Usually there is only one file (.elp), but it can be several files - 

    // depends on your project options and your customization code
    for X := 1 to AttachedFiles.Count - 1 do
      Win32Check(CopyFile(PChar(AttachedFiles[X]), 

        PChar(Folder + ExtractFileName(AttachedFiles[X])), False));
  end;
 
begin
  Finalize(Result);
  FillChar(Result, SizeOf(Result), 0);
  try
    if AttachedFiles.Count = 0 then
    begin
      // We should not really get there, this is just fail-safe check
      Result.SendResult := srNoExceptionInfo;
      Exit;
    end;
 
 
 
    CopyBugReport(GetFolderAppData + 'YourApp' + PathDelim + 'BugReports');
 
    // Alternative:
    // CopyBugReport(ExpandEnvVars(Options.CustomField['Custom_BugReportFolder']));
    //
    // You must enter "Custom_BugReportFolder" value in Custom / Manual page 

    // of EurekaLog's project options, e.g.:
    // Custom_BugReportFolder="%APPDATA%\\YourApp\\BugReports"
    //
    // See for more info
 
 
 
    // Indicate that "send" was a success.
    Result.SendResult := srSent;
 
  except
    on E: Exception do
    begin
      // Indicate that "send" was a failure.
      Result.SendResult := srUnknownError;
      if E is EOSError then
        Result.ErrorCode := Integer(EOSError(E).ErrorCode);
      Result.ErrorMessage := E.Message;
    end;
  end;
end;
 
initialization
  // Register our fake send engine
  RegisterSenderFirst(TSaveToFolder);
 
  // Once engine is registered - we can use it
  CurrentEurekaLogOptions.SenderClasses := TSaveToFolder.ClassName;
  // This registers our fake send engine as the only send method
 
  // Alternative:
  // CurrentEurekaLogOptions.AddSenderClass(TSaveToFolder.ClassName);
  // This registers our fake send engine as last resort fallback send method
  // E.g. it will only be used if send fails completely
end.

 


 

Background sender

 

Code which will perform actual send can use the following code. The example below assumes that the example above was used to store bug reports in the offline storage. E.g. bug report files were saved to sub-folders, and sending settings were saved to params.json file. The example below will use Jira sender, but you may replace it with any other sender. You may use this code as is; you may modify it; or you may write your own code based on this example.

 

uses
  ESendAPIJIRA, // for TELTrackerJIRASender - replace for your actual sender
  EClasses,     // for TEurekaModuleOptions
  EJSON,        // for JSONCreate and IJSONValues
  EEncoding,    // for FileToText
  ETools,       // for DelTree(Ex)
  ETypes;       // for TResponse
 
function SendReport(const AFolder: String): TResponse;
 
  function SendReportEx(const AOptions, AFiles: TStrings): TResponse;
  var

    // Replace for your actual sender
    Sender: TELTrackerJIRASender;
  begin
    Sender := TELTrackerJIRASender.Create;
    try
      Sender.AttachedFiles := AFiles;
 
      // This example assumes that settings for sender are stored

      // in options loaded from params.json file
      // E.g. settings for sending were set up in your real project 

      // (not in current project!)
      Sender.Options.Assign(AOptions);
 
      // Alternative / optional: 

      // you may set up sending settings right here, locally
 
      Result := Sender.SendMessage;
    finally
      FreeAndNil(Sender);
    end;
  end;
 
  procedure JSONToStrings(const AJSONValue: Variant; AList: TStrings);
  var
    Values: IJSONValues;
    X: Integer;
  begin
    Values := JSONValues(AJSONValue);
    for X := 0 to Values.Count - 1 do
      AList.Values[Values.ItemNames[X]] := Values.ItemValues[X];
  end;
 
  procedure CleanupFiles(AFiles: TStrings);
  var
    I: Integer;
  begin
    I := AFiles.IndexOfName('count');
    if I >= 0 then
      AFiles.Delete(I);
    for I := 0 to AFiles.Count - 1 do
      AFiles[I] := AFiles.ValueFromIndex[I];
  end;
 
var
  Folder: String;
  JSON: IJSONValues;
  Options: TStringList;
  Files: TStringList;
begin
  Folder := IncludeTrailingPathDelimiter(AFolder);
  JSON := JSONCreate(FileToText(Folder + 'params.json'));
 
  Options := TStringList.Create;
  Files := TStringList.Create;
  try
    JSONToStrings(JSON['options'], Options);
    JSONToStrings(JSON['files'], Files);
    CleanupFiles(Files);
 
    Result := SendReportEx(Options, Files);
  finally
    FreeAndNil(Files);
    FreeAndNil(Options);
  end;
end;

 

You can call this function like this:

 

var
  Folder: String;
  SR: TSearchRec;
  Reports: TStringList;
  R: TResponse;
  X: Integer;
begin
  Folder := GetFolderAppData + 'YourApp' + PathDelim + 'BugReports' + PathDelim;

  // Alternative:
  // Folder := ExpandEnvVars(CurrentEurekaLogOptions.CustomField['Custom_BugReportFolder']);
  //
  // You must enter "Custom_BugReportFolder" value in Custom / Manual page 

  // of EurekaLog's project options, e.g.:
  // Custom_BugReportFolder="%APPDATA%\\YourApp\\BugReports"
  //
  // See for more info
 

 
  Reports := TStringList.Create;
  try
    if FindFirst(Folder + '*.*', faDirectory, SR) = 0 then
    try
      repeat
        if (SR.Name <> '.'and (SR.Name <> '..'then
          Reports.Add(Folder + SR.Name);
      until FindNext(SR) <> 0;
    finally
      FindClose(SR);
    end;
 
    for X := 0 to Reports.Count - 1 do
    begin
      R := SendReport(Reports[X]);
      if Succeeded(HRESULT(R.SendResult)) then
        DelTree(Reports[X]);
 
      // You may also analyze R and, for example, show user some feedback messages
      // See EDialog.pas, TBaseDialog.ShowSendResult for a sample implementation
 
    end;
 
  finally
    FreeAndNil(Reports);
  end;
end;

 

If you intend to running this code in a background process - then you probably want to subscribe to change notifications for %APPDATA%\YourApp\BugReports folder.

 


 

Conclusion

 

Please note that the code above is just an example. You still have to decide how you are going to manage offline storage, how and when launch sending, etc. You would need to write more code.

 

 

See also:




Send feedback... Build date: 2023-09-11
Last edited: 2023-08-09
PRIVACY STATEMENT
The documentation team uses the feedback submitted to improve the EurekaLog documentation. We do not use your e-mail address for any other purpose. We will remove your e-mail address from our system after the issue you are reporting has been resolved. While we are working to resolve this issue, we may send you an e-mail message to request more information about your feedback. After the issues have been addressed, we may send you an email message to let you know that your feedback has been addressed.


Permanent link to this article: https://www.eurekalog.com/help/eurekalog/offline_storage.php