mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-24 00:27:40 +00:00
289 lines
8.8 KiB
ObjectPascal
289 lines
8.8 KiB
ObjectPascal
(*
|
|
Name: DebugManager
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 14 Sep 2006
|
|
|
|
Modified Date: 9 Aug 2015
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 14 Sep 2006 - mcdurdin - Initial version
|
|
04 Jan 2007 - mcdurdin - Add ShouldDebug function
|
|
19 Jun 2007 - mcdurdin - Widestring it
|
|
19 Nov 2007 - mcdurdin - I1157 - const string parameters
|
|
04 May 2010 - mcdurdin - I2348 - Rework columns in debug log
|
|
04 May 2010 - mcdurdin - I2349 - Hotkey to capture debug log
|
|
04 May 2010 - mcdurdin - I2350 - Keyman.exe should be able to report events in debug log
|
|
04 May 2010 - mcdurdin - I2352 - Debug logging not reliable in some apps due to security
|
|
15 Jun 2010 - mcdurdin - I2423 - Fix crash in debug manager
|
|
24 Jun 2010 - mcdurdin - I2422 - Recreate, not rewrite existing log files
|
|
29 Jun 2010 - mcdurdin - I2446 - Keyman Engine crashes due to debug manager being freed late
|
|
17 Dec 2010 - mcdurdin - Add extra column to raw strings
|
|
11 Jan 2011 - mcdurdin - I2640 - Raise error at appropriate location so it isn't masked by cascading issue
|
|
31 Jan 2011 - mcdurdin - I2685 - Reduce registry noise from ShouldDebug function
|
|
31 Jan 2011 - mcdurdin - I2690 - Add foreground window info to debug log
|
|
31 Jan 2011 - mcdurdin - I2691 - Fix handle leaks
|
|
18 Feb 2011 - mcdurdin - I2685 : 9827 - Fix shoulddebug refresh counter not resetting
|
|
03 May 2011 - mcdurdin - I2890 - Record diagnostic data when encountering registry errors
|
|
18 May 2012 - mcdurdin - I3306 - V9.0 - Remove TntControls + Win9x support
|
|
08 Jun 2012 - mcdurdin - I3310 - V9.0 - Unicode in Delphi fixes
|
|
28 Nov 2012 - mcdurdin - I3601 - V9.0 - Debug messages were written partially Unicode, partially ANSI
|
|
13 Dec 2012 - mcdurdin - I3656 - V9.0 - Debug logs show too many columns for host messages
|
|
01 Jan 2013 - mcdurdin - I3692 - V9.0 - [host] entries in system.log out by 1 tabstop
|
|
09 Aug 2015 - mcdurdin - I4843 - Log reported modifier state as well as Keyman current modifier state
|
|
*)
|
|
unit Keyman.System.DebugLogManager; // I3306
|
|
|
|
interface
|
|
|
|
uses
|
|
System.Classes,
|
|
System.SysUtils,
|
|
Winapi.Windows,
|
|
JwaWmiStr,
|
|
JwaEventTracing,
|
|
|
|
Keyman.System.DebugLogCommon,
|
|
UserMessages;
|
|
|
|
type
|
|
TDebugLogManager = class
|
|
private
|
|
FOwner: HWND;
|
|
FDebugLogIndex: Integer;
|
|
FDebugLogFileName: string;
|
|
|
|
pSessionProperties: PEVENT_TRACE_PROPERTIES;
|
|
FSessionHandle: TRACEHANDLE;
|
|
FTraceRunning: Boolean;
|
|
|
|
procedure StartNewLogFile;
|
|
procedure FindFirstLogFileName;
|
|
|
|
function DebugLogFileName(n: Integer): string;
|
|
public
|
|
constructor Create(AOwner: HWND); reintroduce;
|
|
destructor Destroy; override;
|
|
|
|
end;
|
|
|
|
function GetDebugManager(AOwner: HWND): TDebugLogManager;
|
|
|
|
implementation
|
|
|
|
uses
|
|
Accctrl,
|
|
AclApi,
|
|
ErrorControlledRegistry,
|
|
ErrLogPath,
|
|
RegistryKeys,
|
|
Unicode;
|
|
|
|
var
|
|
FDebugManager: TDebugLogManager = nil;
|
|
FTerminating: Boolean = False;
|
|
|
|
FShouldDebug: Boolean = False;
|
|
FShouldDebugLastTick: Cardinal = 0;
|
|
|
|
const
|
|
// GUID that identifies your trace session.
|
|
// Remember to create your own session GUID.
|
|
SessionGuid: TGUID = '{FADEA67E-0EE9-452B-AF04-22E342D1227A}';
|
|
|
|
function GetDebugManager(AOwner: HWND): TDebugLogManager;
|
|
begin
|
|
Assert(not FTerminating);
|
|
|
|
if ShouldDebug then
|
|
begin
|
|
if not Assigned(FDebugManager) then
|
|
FDebugManager := TDebugLogManager.Create(AOwner);
|
|
Result := FDebugManager;
|
|
end
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
{ TDebugManager }
|
|
|
|
const LOGSESSION_NAME: string = 'Keyman Event Trace Session';
|
|
|
|
function StringBufferSize(const s: string): Integer;
|
|
begin
|
|
Result := (Length(s) + 1) * sizeof(WCHAR);
|
|
end;
|
|
|
|
constructor TDebugLogManager.Create(AOwner: HWND);
|
|
var
|
|
BufferSize: ULONG;
|
|
status: ULONG;
|
|
// BOOL TraceOn = TRUE;
|
|
// BOOL bTraceAlreadyStarted = FALSE;
|
|
begin
|
|
FOwner := AOwner;
|
|
|
|
FindFirstLogFileName;
|
|
StartNewLogFile;
|
|
|
|
// Allocate memory for the session properties. The memory must
|
|
// be large enough to include the log file name and session name,
|
|
// which get appended to the end of the session properties structure.
|
|
|
|
BufferSize := sizeof(EVENT_TRACE_PROPERTIES) + StringBufferSize(FDebugLogFileName) + StringBufferSize(LOGSESSION_NAME);
|
|
pSessionProperties := PEVENT_TRACE_PROPERTIES(AllocMem(BufferSize));
|
|
|
|
// Set the session properties. You only append the log file name
|
|
// to the properties structure; the StartTrace function appends
|
|
// the session name for you.
|
|
|
|
pSessionProperties.Wnode.BufferSize := BufferSize;
|
|
pSessionProperties.Wnode.Flags := WNODE_FLAG_TRACED_GUID;
|
|
pSessionProperties.Wnode.ClientContext := 1; //QPC clock resolution
|
|
pSessionProperties.Wnode.Guid := SessionGuid;
|
|
pSessionProperties.LogFileMode := EVENT_TRACE_FILE_MODE_SEQUENTIAL;
|
|
pSessionProperties.MaximumFileSize := 1; // 1 MB
|
|
pSessionProperties.LoggerNameOffset := sizeof(EVENT_TRACE_PROPERTIES);
|
|
pSessionProperties.LogFileNameOffset := sizeof(EVENT_TRACE_PROPERTIES) + sizeof(LOGSESSION_NAME);
|
|
StrPCopy(PWideChar(PByte(pSessionProperties) + pSessionProperties.LoggerNameOffset), LOGSESSION_NAME);
|
|
StrPCopy(PWideChar(PByte(pSessionProperties) + pSessionProperties.LogFileNameOffset), FDebugLogFileName);
|
|
// StringCbCopy((LPWSTR)((char*)pSessionProperties + pSessionProperties->LogFileNameOffset), sizeof(LOGFILE_PATH), LOGFILE_PATH);
|
|
|
|
status := StartTrace(@FSessionHandle, PWideChar(LOGSESSION_NAME), pSessionProperties^);
|
|
if ERROR_ALREADY_EXISTS = status then
|
|
begin
|
|
// The trace was already started, perhaps Keyman did not close down cleanly
|
|
// We'll stop it and restart it
|
|
status := ControlTraceW(FSessionHandle, PWideChar(LOGSESSION_NAME), pSessionProperties^, EVENT_TRACE_CONTROL_STOP);
|
|
if ERROR_SUCCESS <> status then
|
|
OutputDebugString(PChar('ControlTrace failed with '+IntToStr(status)));
|
|
|
|
status := StartTrace(@FSessionHandle, PWideChar(LOGSESSION_NAME), pSessionProperties^);
|
|
end;
|
|
|
|
if ERROR_SUCCESS <> status then
|
|
RaiseLastOSError(status, 'StartTrace');
|
|
|
|
// Enable the providers that you want to log events to your session.
|
|
|
|
status := EnableTraceEx(
|
|
@DebugLogProviderGuid,
|
|
@SessionGuid,
|
|
FSessionHandle,
|
|
1, // Enable
|
|
TRACE_LEVEL_INFORMATION,
|
|
0,
|
|
0,
|
|
0,
|
|
nil
|
|
);
|
|
|
|
if ERROR_SUCCESS <> status then
|
|
RaiseLastOSError(status, 'EnableTraceEx');
|
|
|
|
FTraceRunning := True;
|
|
end;
|
|
|
|
destructor TDebugLogManager.Destroy;
|
|
var
|
|
status: ULONG;
|
|
begin
|
|
if FSessionHandle <> 0 then
|
|
begin
|
|
if FTraceRunning then
|
|
begin
|
|
status := EnableTraceEx(
|
|
@DebugLogProviderGuid,
|
|
@SessionGuid,
|
|
FSessionHandle,
|
|
0, // Disable
|
|
TRACE_LEVEL_INFORMATION,
|
|
0,
|
|
0,
|
|
0,
|
|
nil
|
|
);
|
|
if ERROR_SUCCESS <> status then
|
|
OutputDebugString(PChar('EnableTraceEx(FALSE) failed with '+IntToStr(status)));
|
|
|
|
FTraceRunning := False;
|
|
end;
|
|
|
|
// We use ControlTraceW because JwaEventTracing has a typo for ControlTrace
|
|
status := ControlTraceW(FSessionHandle, PWideChar(LOGSESSION_NAME), pSessionProperties^, EVENT_TRACE_CONTROL_STOP);
|
|
if ERROR_SUCCESS <> status then
|
|
OutputDebugString(PChar('ControlTrace failed with '+IntToStr(status)));
|
|
|
|
FSessionHandle := 0;
|
|
end;
|
|
|
|
if pSessionProperties <> nil then
|
|
begin
|
|
FreeMem(pSessionProperties);
|
|
pSessionProperties := nil;
|
|
end;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
const
|
|
MAXDEBUGLOGS = 16;
|
|
|
|
function TDebugLogManager.DebugLogFileName(n: Integer): string;
|
|
begin
|
|
Result := GetErrLogPath + 'system' + IntToStr(n) + '.etl';
|
|
end;
|
|
|
|
procedure TDebugLogManager.FindFirstLogFileName;
|
|
var
|
|
FSelectedTime: TDateTime;
|
|
i: Integer;
|
|
f: TSearchRec;
|
|
begin
|
|
FSelectedTime := MaxDateTime;
|
|
FDebugLogIndex := 0;
|
|
|
|
for i := 0 to MAXDEBUGLOGS - 1 do
|
|
begin
|
|
if FindFirst(DebugLogFileName(i), 0, f) = 0 then
|
|
begin
|
|
if f.TimeStamp < FSelectedTime then
|
|
begin
|
|
FDebugLogIndex := i;
|
|
FSelectedTime := f.TimeStamp;
|
|
end;
|
|
System.SysUtils.FindClose(f);
|
|
end
|
|
else
|
|
begin
|
|
FDebugLogIndex := i;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
Dec(FDebugLogIndex); // It will be immediately incremented by StartNewLogFile
|
|
end;
|
|
|
|
procedure TDebugLogManager.StartNewLogFile;
|
|
begin
|
|
Inc(FDebugLogIndex);
|
|
if FDebugLogIndex > MAXDEBUGLOGS then
|
|
FDebugLogIndex := 0;
|
|
|
|
FDebugLogFileName := DebugLogFileName(FDebugLogIndex);
|
|
if FileExists(FDebugLogFileName) then
|
|
System.SysUtils.DeleteFile(FDebugLogFileName);
|
|
end;
|
|
|
|
|
|
initialization
|
|
finalization
|
|
FTerminating := True;
|
|
FreeAndNil(FDebugManager);
|
|
end.
|