The OpenEdge Memory Profiler is the preferred method for identifying memory leaks because it is more accurate, as it directly tracks every object at the AVM level. However, you can also gather information about memory leaks from AVM log files by using the OpenEdge.Core.Util.LeakCheck class, as described in the following steps.

  1. Configure logging for dynamic objects by directing LOG-MANAGER output to a dedicated log file and enabling DynObjects.* logging.
  2. Run the code you want to analyze while dynamic object logging is enabled, so object creation and deletion events are recorded.
  3. Disable dynamic object logging to limit logging overhead to the targeted code section.
  4. Parse the generated log file using the OpenEdge.Core.Util.LeakCheck class.
  5. Check for detected leaks and, if present, generate a JSON report describing the leaked objects.

The following example shows a simple, self‑contained way to enable dynamic object logging for a specific section of code and then use the OpenEdge.Core.Util.LeakCheck class to analyze the resulting log file for potential memory leaks.

BLOCK-LEVEL ON ERROR UNDO, THROW.

USING OpenEdge.Core.Util.LeakCheck.
USING Progress.Json.ObjectModel.JsonObject.

// Define primitive variables for this example. 
VAR CHARACTER cLogFile = "myapp.log", cOldLogEntryTypes = "".
VAR LOGICAL lLeaksFound = FALSE.

// Clear any existing copy of the log file for this procedure.
FILE-INFO:FILE-NAME = cLogFile.
IF FILE-INFO:FULL-PATHNAME NE ? THEN
  OS-DELETE value(cLogFile) no-error.

// Demonstrate direct logging of data while executing code.
LOG-MANAGER:LOGFILE-NAME = cLogFile.
cOldLogEntryTypes = LOG-MANAGER:LOG-ENTRY-TYPES.
LOG-MANAGER:LOG-ENTRY-TYPES = "DynObjects.*:5".

// Execute your suspect code at this point, generating log output to process.
// In this example we can create a JsonObject instance but not delete it.
// For comparison, run this again after commenting out the 2 lines below.
DEFINE VARIABLE oEmpty AS JsonObject NO-UNDO.
oEmpty = NEW JsonObject().

// Restore the prior log entry types (turns off DynObjects).
LOG-MANAGER:LOG-ENTRY-TYPES = cOldLogEntryTypes.

// Create the variables for the LeakCheck object and report.
DEFINE VARIABLE oChecker AS LeakCheck NO-UNDO.
DEFINE VARIABLE oReport AS JsonObject NO-UNDO.

// Instantiate the OpenEdge.Core.Util.LeakCheck class.
ASSIGN oChecker = NEW LeakCheck().

// Call the ParseLog method with the log file just generated.
IF oChecker:ParseLog(cLogFile) THEN DO:
  ASSIGN lLeaksFound = oChecker:HasLeaks().

  // The leak checker reads the DYNOBJECT log lines
  // Query the HasLeaks() method to check for leaks.
  MESSAGE 'checker:HasLeaks()=' oChecker:HasLeaks()
    SKIP VIEW-AS ALERT-BOX.

  // If there are leaks, the GetReport() method returns a JSON object 
  //   containing information about the leak.
  // Leaks are grouped by type, for example, DATA, XML, BLOB, PLO, WIDGET-POOL,
  //   HANDLE, PROCEDURE and session id.
  IF lLeaksFound THEN DO:
    ASSIGN oReport = oChecker:GetReport().
    oReport:WriteFile(SUBSTITUTE("&1/leak_report.json", 
      RIGHT-TRIM(REPLACE(SESSION:TEMP-DIR, "~\", "/"), "/")), YES).
  END.
END.

The report output looks similar to the following:

Note: In this report, line 5 identifies one memory leak. At line 21, a file name appears along with “@ 24“ indicating the line in that code as the source of the leak. In this example, it was an object of type “Progress.Json.ObjectModel.JsonObject” (line 19), which was CREATEd (line 28), and reported as a leaked object since it had no deletion action identified in the log file.